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

#include <stdlib.h>     // bsearch
#include "sta.h"

int tst_session_start(struct tst_session *sess)
{
    int i;
    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 id_base = 0x0500;
    struct pwrfip_node_cfg node_cfg;
    struct pwrfip_var_cfg var_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_VAR_COUNT_MAX];
    int ba_req_count = sess->station_max_count + 1;

    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));

    /* 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 => disable */
    node_cfg.msg.enable = 0;
    /* ba configuration (if master active) */
    node_cfg.ba.enable = (station_number < 16) ? 1 : 0;
    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 = sess->station_max_count,
            .max_prio = 0,
            .my_phy_addr = station_number,
            .my_prio = station_number,
        };
        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;
        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;

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

    /* app handlers */
    node_cfg.pwrfip_msg_recv_handler = NULL;
    node_cfg.pwrfip_msg_send_handler = NULL;
    node_cfg.pwrfip_urg_req_handler = NULL;
    node_cfg.pwrfip_nor_req_handler = NULL;
    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");
    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;
    }

    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 */
    /*      -> build produced varlist */
    var_cfg.type = PWRFIP_VAR_TYPE_PROD;
    var_cfg.id = id_base + station_number;
    /* prod cfg */
    var_cfg.prod.payload_bsz = 8;
    var_cfg.prod.flags = \
        /* enable prod status */
        PWRFIP_VAR_FLAGS_REFRESH;
    var_cfg.prod.refreshment_ustime = 2 * sess->ba_cycle_ustime;
    /* event cfg */
    var_cfg.prod.evt_type = PWRFIP_EVT_TYPE_NONE;
    var_cfg.pwrfip_var_handler = NULL;
    sess->node.prod_varlist[0] = pwrfip_var_create(sess->node.al, &var_cfg);
    if (!sess->node.prod_varlist[0]) {
        _print(station_number, err, "produced variable creation failed: %s\n",
            pwrfip_strerror(errno));
        goto fail1;
    }
    sess->node.prod_varcount++;

    /*      -> build consumed varlist */
    var_cfg.type = PWRFIP_VAR_TYPE_CONS;
    for (i = 0; i < sess->station_max_count; ++i) {
        int idx = sess->node.cons_varcount;
        if (i == station_number)
            continue;
        var_cfg.id = id_base + i;
        /* cons cfg */
        var_cfg.cons.payload_bsz = 8;
        var_cfg.cons.flags = \
            /* enable prod status */
            PWRFIP_VAR_FLAGS_REFRESH | \
            /* enable promptness checking */
            PWRFIP_VAR_FLAGS_PROMPT | \
            /* enable pdu + len bytes checking */
            PWRFIP_VAR_FLAGS_CHK_PDU_LEN;
        var_cfg.cons.promptness_ustime = 2 * sess->ba_cycle_ustime;
        /* event cfg */
        var_cfg.cons.evt_type = PWRFIP_EVT_TYPE_NONE;
        var_cfg.pwrfip_var_handler = NULL;
        sess->node.cons_varlist[idx] = pwrfip_var_create(sess->node.al, &var_cfg);
        if (!sess->node.cons_varlist[idx]) {
            _print(station_number, err,
                "consumed variable creation failed: %s\n",
                pwrfip_strerror(errno));
            goto fail1;
        }
        sess->node.cons_varcount++;
    }

    /* create the bus arbiter macrocycles */
    /* mcycle 1 */
    /* => build a periodic requests list [for periodic mcycle wind] */
    for (i = 0; i < sess->station_max_count; ++i) {
        ba_req[i].type = PWRFIP_BA_ID_DAT;
        ba_req[i].id = id_base + i;
    }
    /* add ba synchro id (0x9003) */
    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 = ba_req_count;
    ba_wind_cfg[0].per.req_list = &ba_req[0];
    /*    2 - aperiodic variable window => presence test */
    ba_wind_cfg[1].type = PWRFIP_BA_WIND_APER_VAR;
    ba_wind_cfg[1].aper_var.end_ustime = sess->ba_cycle_ustime * 0.9; /* end at 90% of mcycle */
    ba_wind_cfg[1].aper_var.enable_testp = 1, /* enable presence test if no other
                                               * request to dispatch */
    /*    3 - resync wait window */
    ba_wind_cfg[2].type = PWRFIP_BA_WIND_WAIT;
    ba_wind_cfg[2].wait.end_ustime = sess->ba_cycle_ustime;
    ba_wind_cfg[2].wait.is_silent = 0; /* fill window with padding frames */
    ba_wind_cfg[2].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 fail1;
    }

    _print(station_number, info, "[fip] node start\n");
    if (pwrfip_node_start(sess->node.al, &sess->node.mc_list[0],
            TST_BA_MCYCLE_CNT)) {
        _print(station_number, err, "node starting failed: %s\n",
            pwrfip_strerror(errno));
        goto fail2;
    }

    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 fail2;
        }
    }

    return 0;

fail2: /* 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]);
fail1: /* var+aele deletion. pwrfip_node_exit does the job also */
    for (i = 0; i < sess->node.cons_varcount; ++i)
        pwrfip_var_delete(sess->node.cons_varlist[i]);

    for (i = 0; i < sess->node.prod_varcount; ++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;

    /* 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;
}

static int addr_search(const void *a, const void *b) {
    uint8_t id_a = *(uint8_t *)a;
    uint8_t id_b = *(*(uint8_t **)b);
    if (id_a == id_b)
        return 0;
    return (id_a < id_b) ? -1 : 1;
};

void tst_display_update(struct tst_session* sess)
{
    msg_src src = sess->station_number;
    msg_type type;
    struct pwrfip_node_infos *fip_infos = sess->node.n->infos;
    const char *fip_bitrate_str = \
        (fip_infos->cfg.fip_bitrate < PWRFIP_BITRATE_RANGE) ? \
        bitrate[fip_infos->cfg.fip_bitrate - _PWRFIP_BITRATE_MIN] : "Unknown\0";
    const char *fip_frame_type_str = \
        (fip_infos->cfg.fip_frm_type == PWRFIP_FRM_WORLDFIP) ? \
            "WorldFIP\0" : "FIP\0";
    struct pwrfip_node_report report;
    struct pwrfip_sm_presence_list presence_list;
    uint8_t *presence_tab_ptr[256];
    uint16_t fd_status;
    uint64_t phy_err = 0, dl_err = 0, app_err = 0, avg_duration = 0;
    uint64_t w_var_warn = 0, r_var_warn = 0;
    int i, j;

    for (i = 1; i < _PWRFIP_VAR_ERR_MAX; ++i) {
        w_var_warn += sess->stats.w_var_err[i];
        r_var_warn += sess->stats.r_var_err[i];
    }

    printf("%c[;H%c[2J",27,27);

    _print(src, info, "FIP: (%s@%s)\n", fip_frame_type_str, fip_bitrate_str);
    _print(src, info, "  Address    : %d (Hexa=0x%02x)\n",
        fip_infos->cfg.fip_phy_addr, fip_infos->cfg.fip_phy_addr);
    _print(src, info, "  Factory SN : 0x%" PRIx64 "\n", fip_infos->cpu_id >> 16);
    _print(src, info, "  Segment    : %d\n", fip_infos->cfg.fip_seg_num);
    _print(src, info, "  Turn-Around: %dus\n", fip_infos->cfg.turn_around_ustime);
    _print(src, info, "  Silence    : %dus\n", fip_infos->cfg.silence_ustime);
    _print(src, info, "  Cycles     : %"PRIu64" (T=%.1fms)\n",
        sess->stats.nb_sync_cb,
        ((float) sess->stats.min_sync_cb_last) / 1000);
    _print(src, info, "  BA State   : %s\n", 
        pwrfip_ba_state_str[sess->node.ba_status.state]);

    avg_duration = (!sess->stats.nb_sync_cb) ? 0 : \
                    ((float) sess->stats.cumul_sync_cb_duration) \
                        / sess->stats.nb_sync_cb;
    _print(src, info,
        "  Sync_Dur   : min=%"PRIu64"us, avg=%"PRIu64"us, max=%"PRIu64"us\n",
        sess->stats.min_sync_cb_duration, avg_duration,
        sess->stats.max_sync_cb_duration);

    if (pwrfip_node_report_get(sess->node.n, &report))
        return;

    fd_status = report.medium_status.state;
    type = (fd_status & PWRFIP_MEDIUM_STATE_CH1_VALID) ? info : err;
    _print(src, type, "  Channel 1  : %s [tx_err=%3s wdg_err=%3s]\n",
        (fd_status & PWRFIP_MEDIUM_STATE_CH1_VALID) ? "active  ": "inactive",
        (fd_status & PWRFIP_MEDIUM_STATE_CH1_TX_ERROR) ? "yes": "no",
        (fd_status & PWRFIP_MEDIUM_STATE_CH1_WATCHDOG) ? "yes": "no");
    type = (fd_status & PWRFIP_MEDIUM_STATE_CH2_VALID) ? info : err;
    _print(src, type, "  Channel 2  : %s [tx_err=%3s wdg_err=%3s]\n",
        (fd_status & PWRFIP_MEDIUM_STATE_CH2_VALID) ? "active  ": "inactive",
        (fd_status & PWRFIP_MEDIUM_STATE_CH2_TX_ERROR) ? "yes": "no",
        (fd_status & PWRFIP_MEDIUM_STATE_CH2_WATCHDOG) ? "yes": "no");

    phy_err = report.rx_err.pre_mis + report.rx_err.fsd_mis + \
              report.rx_err.fsd_unk + report.rx_err.fed_mis;
    dl_err = report.rx_err.crc_bad;
    app_err = report.rx_err.pdu_bad + report.rx_err.len_bad + \
              report.rx_err.not_fresh + report.rx_err.not_prompt;
    _print(src, info, "  RX frames  : ok=%"PRIu64"\n", report.rx_err.ok);

    if (phy_err || dl_err || app_err) {
        _print(src, err, "               rx_total_err=%"PRIu64"\n",
            phy_err + dl_err + app_err);

        if (phy_err) {
            _print(src, err, "                - phy_err\n");
            if (report.rx_err.pre_mis)
                _print(src, err, "                 + pre_mis=%"PRIu64"\n",
                    report.rx_err.pre_mis);
            if (report.rx_err.fsd_mis)
                _print(src, err, "                 + fsd_mis=%"PRIu64"\n",
                    report.rx_err.fsd_mis);
            if (report.rx_err.fsd_unk)
                _print(src, err, "                 + fsd_unk=%"PRIu64"\n",
                    report.rx_err.fsd_unk);
            if (report.rx_err.fed_mis)
                _print(src, err, "                 + fed_mis=%"PRIu64"\n",
                    report.rx_err.fed_mis);
        }
        if (dl_err) {
            _print(src, err, "                - dl_err\n");
            if (report.rx_err.crc_bad)
                _print(src, err, "                 + crc_bad=%"PRIu64"\n",
                    report.rx_err.crc_bad);
        }
        if (app_err) {
            _print(src, err, "                - app_err\n");
            if (report.rx_err.pdu_bad)
                _print(src, err, "                 + pdu_bad=%"PRIu64"\n",
                    report.rx_err.pdu_bad);
            if (report.rx_err.len_bad)
                _print(src, err, "                 + len_bad=%"PRIu64"\n",
                    report.rx_err.len_bad);
            if (report.rx_err.not_fresh)
                _print(src, err, "                 + not_fresh=%"PRIu64"\n",
                    report.rx_err.not_fresh);
            if (report.rx_err.not_prompt)
                _print(src, err, "                 + not_prompt=%"PRIu64"\n",
                    report.rx_err.not_prompt);
        }
    }

    phy_err = report.tx_err.collision + report.tx_err.consistency;
    dl_err = 0;
    app_err = report.tx_err.not_fresh;
    _print(src, info, "  TX frames  : ok=%"PRIu64"\n", report.tx_err.ok);
    if (phy_err || dl_err || app_err) {
        _print(src, err, "               tx_total_err=%"PRIu64"\n",
            phy_err + dl_err + app_err);

        if (phy_err) {
            _print(src, err, "                - phy_err\n");
            if (report.tx_err.collision)
                _print(src, err, "                 + collision=%"PRIu64"\n",
                    report.tx_err.collision);
            if (report.tx_err.consistency)
                _print(src, err, "                 + consistency=%"PRIu64"\n",
                    report.tx_err.consistency);
        }
        if (app_err) {
            _print(src, err, "                - app_err\n");
            if (report.tx_err.not_fresh)
                _print(src, err, "                 + not_fresh=%"PRIu64"\n",
                    report.tx_err.not_fresh);
        }
    }

    _print(src, info, "\n");
    _print(src, info, "  R_Op       : ok=%"PRIu64"\n", sess->stats.r_var_err[0]);
    if (r_var_warn) {
        _print(src, err, "               r_op_total_warn=%"PRIu64"\n", r_var_warn);
        for (i = 1; i < _PWRFIP_VAR_ERR_MAX; ++i) {
            if (sess->stats.r_var_err[i])
                _print(src, err, "                - warn=%"PRIu64" => %s\n",
                    sess->stats.r_var_err[i], pwrfip_var_err_str[i]);
        }
    }
    _print(src, info, "  W_Op       : ok=%"PRIu64"\n", sess->stats.w_var_err[0]);
    if (w_var_warn) {
        _print(src, err, "               w_op_total_warn=%"PRIu64"\n", w_var_warn);
        for (i = 1; i < _PWRFIP_VAR_ERR_MAX; ++i) {
            if (sess->stats.w_var_err[i])
                _print(src, err, "                - warn=%"PRIu64" => %s\n",
                    sess->stats.w_var_err[i], pwrfip_var_err_str[i]);
        }
    }
    _print(src, info, "\n");

    if (pwrfip_sm_presence_list_get(sess->node.n, &presence_list))
        return;

    for (i = 0; i < presence_list.count; ++i)
        presence_tab_ptr[i] = &presence_list.addr[i];

    printf("\nHexadecimal addresses of present nodes: Node_Count=%d\n\n   ",
        presence_list.count);
    for (i = 0; i < 16; ++i)
        printf("| %02x ", i);
    printf("|\n");

    for (i = 0; i < 16; ++i) {
        printf("%02x ", i * 16);
        for (j = 0; j < 16; ++j) {
            uint8_t sta_num = (i * 16) + j;

            /* binary search among presence_list */
            if (bsearch(&sta_num, &presence_tab_ptr,
                (size_t) presence_list.count,
                sizeof(struct uint8_t *), addr_search)) {
#if defined (_WIN32) || defined (_WIN64)
                printf("| O  ");
#else
                printf("| \u2705 ");
#endif
            }
            else
                printf("|    ");
        }
        printf("|\n");
    }
}

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;
    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;
    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;
    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;
    msg_src src = sess->station_number;
    unsigned long last_us = 0;
    static int is_first = 1;
#if defined(__QNX__) || defined(_WIN32) || defined(_WIN64)
    static uint64_t start_treatment, stop_treatment, last_treatment = 0;
#else
    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 < sess->node.prod_varcount; ++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);

            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);
                    if (var->error < _PWRFIP_VAR_ERR_MAX)
                        sess->stats.w_var_err[var->error]++;
                }
                continue;
            }
            sess->stats.w_var_err[0]++;

            // 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 < sess->node.cons_varcount; ++i) {
            struct pwrfip_var *var = sess->node.cons_varlist[i];

            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 */
                    if (var->error != PWRFIP_VAR_NEVER_RECEIVED) {
                        _print(src, warn, "r_var[0x%04x] state: %s\n",
                        var->id, err_str);
                        //for (int j = 0; j < var->bsz; ++j)
                        //    printf("%02x ", var->buffer[j]);
                        //printf("\n");
                        if (var->error < _PWRFIP_VAR_ERR_MAX)
                            sess->stats.r_var_err[var->error]++;
                    }
                }
                continue;
            }
            sess->stats.r_var_err[0]++;
        }
    }

    _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;
    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);
}
