/*
 * Copyright (C) 2022 EXOLIGENT (www.exoligent.com)
 * Author: Maxime Coroyer <maxime.coroyer@exoligent.com>
 */

#include <string.h>     // memset()
#include <errno.h>

#include "gw.h"

extern int dev_open(struct gateway *gw);
extern int dev_close(struct gateway *gw);
extern void dev_show_all();

void print_node_conf(struct gateway *gw);

int fip_start(struct gateway *gw)
{
    int i;
    const struct pwrfip_version *lib_version = pwrfip_version_get();
    struct pwrfip_node_cfg *n_cfg = &gw->fip.n_cfg;

    /* pwrfip - initialization [mandatory] */
    if (pwrfip_init()) {
        _print(app, err, "[failed] cannot init pwrfip library: %s\n",
            pwrfip_strerror(errno));
        return -1;
    }

    /* exoligent - fip device opening */
    if (dev_open(gw))
        goto fail1;

    /* pwrfip - versions */
    _print(app, info,
        "ua-fip gateway: v%s - opc lib: v%d.%d.%d - fip lib: v%d.%d.%d\n",
        APP_VERSION,
        UA_OPEN62541_VER_MAJOR, UA_OPEN62541_VER_MINOR, UA_OPEN62541_VER_PATCH,
        lib_version->info.major, lib_version->info.minor, lib_version->info.patch);

    _print(app, info, "[ok] gateway configuration read\n");

    /* set node config */
    /* general node configuration */
    /* note: some fields already set up via the parser */
    //n_cfg->fip_phy_addr
    //n_cfg->fip_seg_num
    //n_cfg->fip_frm_type
    //n_cfg->fip_bitrate
    //n_cfg->turn_around_ustime
    //n_cfg->silence_ustime
    //n_cfg->ba.enable
    //n_cfg->ba.priority
    n_cfg->enable_bimedium = 1;

    /* bus arbiter configuration (if master active) */
    if (gw->fip.n_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 = n_cfg->silence_ustime,
            .max_phy_addr = 255,
            .max_prio = 0,
            .my_phy_addr = n_cfg->fip_phy_addr,
            .my_prio = n_cfg->ba.priority,
        };
        pwrfip_ba_startup_calculate(&n_cfg->ba.start_ustime,
                &n_cfg->ba.election_ustime, &ba_stup_cfg);
        _print(fip, info, "[ok] bus arbiter infos\n");
        _print(fip, info, "   start    : %dus\n", n_cfg->ba.start_ustime);
        _print(fip, info, "   election : %dus\n", n_cfg->ba.election_ustime);
        n_cfg->ba.msg_req_fifo_size = 0; /* 0 to set default value (60) */
        n_cfg->ba.urgent_var_req_fifo_size = 0; /* 0 to set default value (60) */
        n_cfg->ba.normal_var_req_fifo_size = 0; /* 0 to set default value (60) */
    }

    /* node identification configuration */
    n_cfg->ident.manufacturer_name = "Exoligent\0";
    n_cfg->ident.model_name = "OPCUA Pub/Sub <-> FIP gateway\0";
    n_cfg->ident.revision = 0x10; /* v1.0 */
    n_cfg->ident.tag_name = NULL;
    n_cfg->ident.vendor_field = NULL;

    /* hardware access */
    n_cfg->dev = gw->fip.dev; /* attach pci device to node configuration */

    /* app handlers */
    n_cfg->pwrfip_var_handler = NULL;
    n_cfg->pwrfip_msg_recv_handler = NULL;
    n_cfg->pwrfip_msg_send_handler = NULL;
    n_cfg->pwrfip_urg_req_handler = NULL;
    n_cfg->pwrfip_nor_req_handler = NULL;
    n_cfg->pwrfip_ba_state_handler = NULL;
    n_cfg->pwrfip_ba_sync_handler = NULL;
    n_cfg->pwrfip_error_handler = fip_error_handler;
    n_cfg->pwrfip_reset_handler = fip_reset_handler;

    /**
     * If the gateway configuration file does not define FIP read and/or write
     * operation events, they are automatically recorded here on the BA synchro
     * event [ID(0x9003)]
     */
    if (!gw->fip.has_r_op_evt || !gw->fip.has_w_op_evt) {
        if (!gw->fip.has_r_op_evt && !gw->fip.has_w_op_evt)
            n_cfg->pwrfip_ba_sync_handler = fip_id_sync_rw_op_handler;
        else if (!gw->fip.has_r_op_evt)
            n_cfg->pwrfip_ba_sync_handler = fip_id_sync_r_op_handler;
        else
            n_cfg->pwrfip_ba_sync_handler = fip_id_sync_w_op_handler;
    }

    //print_node_conf(gw);

    /**
     * User app context
     * note: Useful to retrieve the session context from a pwrfip callback
     */
    n_cfg->user_ctx = (void*) gw;

    gw->fip.n = pwrfip_node_init(n_cfg);
    if (!gw->fip.n) {
       _print(fip, err, "[failed] fip node load: %s\n",
            pwrfip_strerror(errno));
        goto fail2;
    }
    _print(fip, info, "[ok] fip node load\n");

    // print_node_infos(gw->fip.n);

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

    /* => create fip variables */
    for (i = 0; i < gw->cfg_varcnt; ++i) {
        struct pwrfip_var_cfg *var_cfg = &gw->cfg_varlist[i].pwrfip_cfg;
        struct pwrfip_var *var;
        switch (var_cfg->type)
        {
        case PWRFIP_VAR_TYPE_PROD:
            var = pwrfip_var_create(gw->fip.al, var_cfg);
            if (!var) {
                _print(fip, err, "[failed] produced variable creation: %s\n",
                    pwrfip_strerror(errno));
                goto fail4;
            }
            gw->fip.prod_varlist[gw->fip.prod_varcnt] = var;
            gw->fip.prod_varcnt++;
        break;
        case PWRFIP_VAR_TYPE_CONS:
            var = pwrfip_var_create(gw->fip.al, var_cfg);
            if (!var) {
                _print(fip, err, "[failed] consumed variable creation: %s\n",
                    pwrfip_strerror(errno));
                goto fail4;
            }
            gw->fip.cons_varlist[gw->fip.cons_varcnt] = var;
            gw->fip.cons_varcnt++;
        break;
        case PWRFIP_VAR_TYPE_SYNC:
            var = pwrfip_var_create(gw->fip.al, var_cfg);
            if (!var) {
                _print(fip, err, "[failed] syncho variable creation: %s\n",
                    pwrfip_strerror(errno));
                goto fail4;
            }
            gw->fip.sync_varlist[gw->fip.sync_varcnt] = var;
            gw->fip.sync_varcnt++;
        break;
        default: break;
        }
    }

    /* create bus arbiter macrocycles */
    for (i = 0; i < gw->fip.mc_cfg_cnt; ++i) {
        struct pwrfip_ba_mcycle_cfg *mcycle_cfg = &gw->fip.mc_cfglist[i].mc_cfg;
        struct pwrfip_ba_mcycle *mcycle;

        mcycle = pwrfip_ba_mcycle_create(gw->fip.n, mcycle_cfg);
        if (!mcycle) {
            _print(fip, err, "[failed] macrocycle creation failed: %s\n",
                pwrfip_strerror(errno));
            goto fail5;
        }
        gw->fip.mc_list[gw->fip.mc_cnt] = mcycle;
        gw->fip.mc_cnt++;
    }

    if (pwrfip_node_start(gw->fip.al, &gw->fip.mc_list[0], gw->fip.mc_cnt)) {
        _print(fip, err, "[failed] fip node start: %s\n",
            pwrfip_strerror(errno));
        goto fail5;
    }
    _print(fip, info, "[ok] fip node start\n");

    if (gw->fip.n_cfg.ba.enable) {
        if (pwrfip_ba_start(gw->fip.mc_list[0])) {
            _print(fip, err, "[failed] bus arbiter start: %s\n",
                pwrfip_strerror(errno));
            goto fail5;
        }
        _print(fip, info, "[ok] bus arbiter start\n");
    }

    return 0;

fail5: /* ba mcycle deletion. pwrfip_node_exit does the job also */
    for (i = 0; i < gw->fip.mc_cnt; ++i)
        pwrfip_ba_mcycle_delete(gw->fip.mc_list[i]);
fail4: /* var+aele deletion. pwrfip_node_exit does the job also */
    for (i = 0; i < gw->fip.cons_varcnt; ++i)
        pwrfip_var_delete(gw->fip.cons_varlist[i]);

    for (i = 0; i < gw->fip.prod_varcnt; ++i)
        pwrfip_var_delete(gw->fip.prod_varlist[i]);

    pwrfip_aele_delete(gw->fip.al);
fail3:
    pwrfip_node_exit(gw->fip.n);
fail2:
    /* exoligent - fip device closure */
    if (dev_close(gw))
        _print(app, err, "[failed] cannot close correctly the fip device.\n");
fail1:
    pwrfip_exit();
    return -1;
}

int fip_stop(struct gateway* gw)
{
    int err = 0;

    /* display node info before close it */
    fip_display_update(gw);

    /* stop the node and delete all resources attached (aele, var, mcycle..) */
    err = pwrfip_node_exit(gw->fip.n);

    /* exoligent - fip device closure */
    if (dev_close(gw))
        _print(fip, err, "[failed] cannot close correctly the fip device.\n");

    pwrfip_exit();

    return err;
}

void fip_display_update(struct gateway *gw)
{
    struct pwrfip_node_infos *fip_infos = gw->fip.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;
    uint16_t fd_status;
    uint64_t phy_err = 0, dl_err = 0, app_err = 0, avg_duration = 0;

    _print(fip, info, "FIP: (%s@%s)\n", fip_frame_type_str, fip_bitrate_str);
    _print(fip, info, "  Address    : %d\n", fip_infos->cfg.fip_phy_addr);
    _print(fip, info, "  Segment    : %d\n", fip_infos->cfg.fip_seg_num);
    _print(fip, info, "  Turn-Around: %dus\n", fip_infos->cfg.turn_around_ustime);
    _print(fip, info, "  Silence    : %dus\n", fip_infos->cfg.silence_ustime);
    _print(fip, info, "  Cycles     : %"PRIu64" (T=%.1fms)\n",
        gw->stats.nb_sync_r_op_cb,
        ((float) gw->stats.max_sync_r_op_cb_last) / 1000);

    avg_duration = (!gw->stats.nb_sync_r_op_cb) ? 0 : \
                    ((float) gw->stats.cumul_sync_r_op_cb_duration) \
                        / gw->stats.nb_sync_r_op_cb;
    _print(fip, info,
        "  Read_Op    : T   => min=%"PRIu64"us, max=%"PRIu64"us\n",
        gw->stats.min_sync_r_op_cb_last,
        gw->stats.max_sync_r_op_cb_last);

    _print(fip, info,
        "               dur => min=%"PRIu64"us, avg=%"PRIu64"us, max=%"PRIu64"us\n",
        gw->stats.min_sync_r_op_cb_duration, avg_duration,
        gw->stats.max_sync_r_op_cb_duration);

    _print(fip, info,
        "               nb  = %"PRIu64"\n", gw->stats.nb_sync_r_op_cb);

    _print(fip, info,
        "               warn= %"PRIu64"\n", gw->stats.r_var_warn);


    avg_duration = (!gw->stats.nb_sync_w_op_cb) ? 0 : \
                    ((float) gw->stats.cumul_sync_w_op_cb_duration) \
                        / gw->stats.nb_sync_w_op_cb;
    _print(fip, info,
        "  Write_Op   : T   => min=%"PRIu64"us, max=%"PRIu64"us\n",
        gw->stats.min_sync_w_op_cb_last,
        gw->stats.max_sync_w_op_cb_last);

    _print(fip, info,
        "               dur => min=%"PRIu64"us, avg=%"PRIu64"us, max=%"PRIu64"us\n",
        gw->stats.min_sync_w_op_cb_duration, avg_duration,
        gw->stats.max_sync_w_op_cb_duration);

    _print(fip, info,
        "               nb  = %"PRIu64"\n", gw->stats.nb_sync_w_op_cb);

    _print(fip, info,
        "               warn= %"PRIu64"\n", gw->stats.w_var_warn);

    _print(fip, info,
        "  Pub_Op     : nb  = %"PRIu64"\n", gw->stats.nb_pub_op);

    _print(fip, info,
        "  Sub_Op     : nb  = %"PRIu64"\n", gw->stats.nb_sub_op);

    if (pwrfip_node_report_get(gw->fip.n, &report))
        return;

    fd_status = report.medium_status.state;
    _print(fip, info, "  Channel 1  : %s\t[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");
    _print(fip, info, "  Channel 2  : %s\t[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(fip, info, "  RX frames  : err=%"PRIu64
        "\t[phy_err=%"PRIu64" dl_err=%"PRIu64" app_err=%"PRIu64"]\n",
        phy_err + dl_err + app_err, phy_err, dl_err, app_err);
    _print(fip, info, "               ok=%"PRIu64"\n", report.rx_err.ok);

    phy_err = report.tx_err.collision + report.tx_err.consistency;
    dl_err = 0;
    app_err = report.tx_err.not_fresh;
    _print(fip, info, "  TX frames  : err=%"PRIu64
        "\t[phy_err=%"PRIu64" dl_err=%"PRIu64" app_err=%"PRIu64"]\n",
        phy_err + dl_err + app_err, phy_err, dl_err, app_err);
    _print(fip, info, "               ok=%"PRIu64"\n", report.tx_err.ok);
}

int fip_r_op(struct gateway *gw)
{
    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);

    /* read loop for input variables */
    for (int i = 0; i < gw->fip.cons_varcnt; ++i) {
        struct pwrfip_var *var = gw->fip.cons_varlist[i];

#if defined(FIP_PING_PONG_TST_TRACE)
        uint64_t prev_r_data = *((uint64_t*) var->buffer);
#endif
        if (pwrfip_var_read(var)) {
            _print(fip, err, "r_var[0x%04x] failed: %s\n", var->id,
                pwrfip_strerror(errno));
            /* we consider this error as fatal error; so we
             * stop the gateway */
            return -1;
        }

#if defined(FIP_PING_PONG_TST_TRACE)
        // compare the previous r_data with new one
        {
            uint64_t r_data  = *((uint64_t*) var->buffer);
            if (prev_r_data != r_data) {
                if (prev_r_data + 1 != r_data) {
                    int64_t diff = r_data - prev_r_data;
                    _print(fip, warn,
                        "ping/pong anomaly: pub=%"PRIu64
                        " <-- cons=%"PRIu64
                        " [diff=%"PRIi64"]\n",
                        prev_r_data, r_data, diff);
                }
            }
        }
#endif
        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(fip, warn, "r_var[0x%04x] state: %s\n",
                        var->id, err_str);
                gw->stats.r_var_warn++;
            }
            continue;
        }
        gw->stats.r_var_ok++;

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

    /* get node report */
    pwrfip_node_report_get(gw->fip.n, &gw->fip.report);

    _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
     * (fip reading operation synchro) */
    if (last_us)
        update_min_max(last_us, &gw->stats.min_sync_r_op_cb_last,
            &gw->stats.max_sync_r_op_cb_last);
    /* => event treatment duration */
    update_stats(&start_treatment, &stop_treatment, &gw->stats.nb_sync_r_op_cb,
        &gw->stats.cumul_sync_r_op_cb_duration,
        &gw->stats.min_sync_r_op_cb_duration,
        &gw->stats.max_sync_r_op_cb_duration);

    return 0;
}

int fip_w_op(struct gateway *gw)
{
    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);

    /* write loop for output variables */
    for (int i = 0; i < gw->fip.prod_varcnt; ++i) {
        struct pwrfip_var *var = gw->fip.prod_varlist[i];

        /* update fip value from opcua */
        if (gw->opc.running) {
            struct gw_var_cfg *v_ctx = var->user_ctx;
            UA_StatusCode srv_status = UA_STATUSCODE_GOOD;

            if (!v_ctx || !gw->opc.srv) {
                _print(fip, warn, "bad context [1]\n");
                goto opc_op_end;
            }

            if (UA_NodeId_isNull(&v_ctx->v_node_id)) {
                _print(fip, warn, "bad context [2]\n");
                goto opc_op_end;
            }

            UA_Variant out;
            UA_Variant_init(&out);
            //UA_Variant_setScalar(&out, &v_ctx->rt_data,
            //    &UA_TYPES[UA_TYPES_BYTESTRING]);

            srv_status = UA_Server_readValue(gw->opc.srv, v_ctx->v_node_id,
                            &out);
            if (srv_status != UA_STATUSCODE_GOOD ||
                !UA_Variant_hasScalarType(&out, &UA_TYPES[UA_TYPES_BYTESTRING])) {
                _print(fip, warn,
                    "failed to read subscribe value (srv=%p,"
                    " srv_status=0x%08x, is_byte_string=%d)\n",
                    gw->opc.srv, srv_status,
                    UA_Variant_hasScalarType(&out, &UA_TYPES[UA_TYPES_BYTESTRING]));
                UA_Variant_clear(&out);
                goto opc_op_end;
            }

            UA_ByteString *p = (UA_ByteString *) out.data;
            if (p)
            {
                if (p->data && (p->length == var->bsz))
                {
#if defined(FIP_PING_PONG_TST_TRACE)
                    // compare the previous w_data with new one
                    {
                        uint64_t prev_w_data = *((uint64_t*) var->buffer);
                        uint64_t w_data  = *((uint64_t*) p->data);
                        if (prev_w_data != w_data) {
                            if (prev_w_data + 1 != w_data) {
                                int64_t diff = w_data - prev_w_data;
                                _print(fip, warn,
                                    "ping/pong anomaly: sub=%"PRIu64
                                    " --> prod=%"PRIu64
                                    " [diff=%"PRIi64"] [fip_sync=%d opc_task=%d]\n",
                                    w_data, prev_w_data, diff,
                                    gw->fip.sync_in_progress,
                                    gw->opc.task_in_progress);
                            }
                        }
                    }
#endif

                    memcpy(var->buffer, p->data, var->bsz);

                    //printf("srv_data: ");
                    //for (int j = 0; j < p->length; ++j)
                    //    printf("%02x ", (uint8_t) p->data[j]);
                    //printf("\n");

                    //printf("rt_data: ");
                    //for (int j = 0; j < var->bsz; ++j)
                    //    printf("%02x ", v_ctx->rt_data.data[j]);
                    //printf("\n");

                    //printf("w_var[0x%04x]: ", var->id);
                    //for (int j = 0; j < var->bsz; ++j)
                    //    printf("%02x ", var->buffer[j]);
                    //printf("\n\n");
                } else {
                    _print(fip, warn,
                        "incorrect subscribe value (p->data=%p,"
                        " p->length=%ld, var->bsz=%d)\n",
                        p->data, p->length, var->bsz);
                }
            } else {
                _print(fip, warn,
                    "incorrect subscribe value (p is null)\n");
            }
            UA_Variant_clear(&out);
        }
opc_op_end:

        if (pwrfip_var_write(var)) {
            _print(fip, err, "w_var[0x%04x] failed: %s\n", var->id,
                pwrfip_strerror(errno));
            /* we consider this error as fatal error; so we
             * stop the gateway */
            return -1;
        }

        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(fip, warn, "w_var[0x%04x] state: %s\n",
                    var->id, err_str);
                gw->stats.w_var_warn++;
            }
            continue;
        }
        gw->stats.w_var_ok++;
    }

    _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
     * (fip writing operation synchro) */
    if (last_us)
        update_min_max(last_us, &gw->stats.min_sync_w_op_cb_last,
            &gw->stats.max_sync_w_op_cb_last);
    /* => event treatment duration */
    update_stats(&start_treatment, &stop_treatment, &gw->stats.nb_sync_w_op_cb,
        &gw->stats.cumul_sync_w_op_cb_duration,
        &gw->stats.min_sync_w_op_cb_duration,
        &gw->stats.max_sync_w_op_cb_duration);

    return 0;
}

void fip_id_sync_r_op_handler(struct pwrfip_node *node,
        struct pwrfip_var *var, struct pwrfip_event *event)
{
    struct gateway *gw = (struct gateway *) node->infos->cfg.user_ctx;

    gw->fip.sync_in_progress = 1;
#if defined FIP_RW_OP_VIA_RT_APP_TASK
    gw->fip.r_op = 1;
#else
    UA_LOCK(&gw->opc.mu);
    if (fip_r_op(gw))
        running = 0;
    UA_UNLOCK(&gw->opc.mu);
#endif
    gw->fip.sync_in_progress = 0;
}

void fip_id_sync_w_op_handler(struct pwrfip_node *node,
        struct pwrfip_var *var, struct pwrfip_event *event)
{
    struct gateway *gw = (struct gateway *) node->infos->cfg.user_ctx;

    gw->fip.sync_in_progress = 1;
#if defined FIP_RW_OP_VIA_RT_APP_TASK
    gw->fip.w_op = 1;
#else
    UA_LOCK(&gw->opc.mu);
    if (fip_w_op(gw))
        running = 0;
    UA_UNLOCK(&gw->opc.mu);
#endif
    gw->fip.sync_in_progress = 0;
}

void fip_id_sync_rw_op_handler(struct pwrfip_node *node,
        struct pwrfip_var *var, struct pwrfip_event *event)
{
    struct gateway *gw = (struct gateway *) node->infos->cfg.user_ctx;

    //_print(fip, info, "fip_id_sync_rw_op_handler\n");
    gw->fip.sync_in_progress = 1;
#if defined FIP_RW_OP_VIA_RT_APP_TASK
    gw->fip.rw_op = 1;
#else
    UA_LOCK(&gw->opc.mu);
    if (fip_r_op(gw))
        running = 0;
    if (fip_w_op(gw))
        running = 0;
    UA_UNLOCK(&gw->opc.mu);
#endif
    gw->fip.sync_in_progress = 0;
}

void fip_id_sync_wr_op_handler(struct pwrfip_node *node,
        struct pwrfip_var *var, struct pwrfip_event *event)
{
    struct gateway *gw = (struct gateway *) node->infos->cfg.user_ctx;

    //_print(fip, info, "fip_id_sync_wr_op_handler\n");
    gw->fip.sync_in_progress = 1;
#if defined FIP_RW_OP_VIA_RT_APP_TASK
    gw->fip.wr_op = 1;
#else
    UA_LOCK(&gw->opc.mu);

    if (gw->opc.task_in_progress)
        _print(fip, warn, "opc task in progress\n");
    if (fip_w_op(gw))
        running = 0;
    if (fip_r_op(gw))
        running = 0;
    UA_UNLOCK(&gw->opc.mu);
#endif
    gw->fip.sync_in_progress = 0;
}

void fip_error_handler(struct pwrfip_node *node,
        enum pwrfip_error_code code)
{
    if (running)
        _print(fip, err,
            "error_handler: %s (err_code=%d)\n",
            pwrfip_strerror(code),
            code);
    running = 0;
}

void fip_reset_handler(struct pwrfip_node *node)
{
    if (pwrfip_device_reset(node->infos->cfg.dev)) {
        _print(fip, err, "reset failed: %s\n", pwrfip_strerror(errno));
        return;
    }
    _print(fip, evt, "reset component (powerfip)\n");
}

void print_node_infos(struct pwrfip_node *node)
{
    struct pwrfip_node_infos *inf = node->infos;

    _print(fip, info, "fip node infos\n");
    _print(fip, info, "   cpu_id   : 0x%" PRIx64 "\n",
        inf->cpu_id);
    _print(fip, info, "   bss_bsz  : %dB \t[max_bsz   : %dB  ]\n",
        inf->bss_bsz, inf->bss_max_bsz);
    _print(fip, info,
        "   gateware : v%d.%d.%d \t[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(fip, info,
        "   firmware : v%d.%d.%d \t[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(fip, info,
        "   driver   : v%d.%d.%d \t[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(fip, info,
        "   library  : v%d.%d.%d \t[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);
}

void print_node_conf(struct gateway *gw)
{
    int i = 0, j = 0;
    struct pwrfip_node_cfg *n_cfg = &gw->fip.n_cfg;

    _print(fip, info, "node_cfg\n");
    _print(fip, info, "  fip_phy_addr      : %d\n", n_cfg->fip_phy_addr);
    _print(fip, info, "  fip_seg_num       : %d\n", n_cfg->fip_seg_num);
    _print(fip, info, "  fip_frm_type      : %d (1: fip, 2: worldfip)\n", n_cfg->fip_frm_type);
    _print(fip, info, "  fip_bitrate       : %d (1: 31.25kbps, 2: 1Mbps, 3: 2.5Mbps, 4: 5Mbps)\n", n_cfg->fip_bitrate);
    _print(fip, info, "  turn_around_ustime: %ld us\n", n_cfg->turn_around_ustime);
    _print(fip, info, "  silence_ustime    : %ld us\n", n_cfg->silence_ustime);
    _print(fip, info, "  enable_bimedium   : %d\n", n_cfg->enable_bimedium);
    _print(fip, info, "  msg.enable        : %d\n", n_cfg->msg.enable);
    _print(fip, info, "  ba.enable         : %d\n", n_cfg->ba.enable);

    _print(fip, info, "var_cfg             : x%d\n", gw->cfg_varcnt);
    for (i = 0; i < gw->cfg_varcnt; ++i)
    {
        struct pwrfip_var_cfg *v_cfg = &gw->cfg_varlist[i].pwrfip_cfg;
        _print(fip, info, "  [%d]\n", i + 1);
        _print(fip, info, "    type            : %d (0: cons, 1: prod)\n", v_cfg->type);
        _print(fip, info, "    id              : 0x%04x\n", v_cfg->id);
        if (v_cfg->type == PWRFIP_VAR_TYPE_CONS)
        {
            _print(fip, info, "    len             : %d\n", v_cfg->cons.payload_bsz);
            _print(fip, info, "    refresh         : %s\n", (v_cfg->cons.flags & PWRFIP_VAR_FLAGS_REFRESH) ? "yes" : "no");
            _print(fip, info, "    prompt          : %s (%ld us)\n",
                (v_cfg->cons.flags & PWRFIP_VAR_FLAGS_PROMPT) ? "yes" : "no",
                (v_cfg->cons.flags & PWRFIP_VAR_FLAGS_PROMPT) ? v_cfg->cons.promptness_ustime : 0);
        } else 
        {
            _print(fip, info, "    len             : %d\n", v_cfg->prod.payload_bsz);
            _print(fip, info, "    refresh         : %s (%ld us)\n",
                (v_cfg->prod.flags & PWRFIP_VAR_FLAGS_REFRESH) ? "yes" : "no",
                (v_cfg->prod.flags & PWRFIP_VAR_FLAGS_REFRESH) ? v_cfg->prod.refreshment_ustime : 0);
            _print(fip, info, "    service\n");
            _print(fip, info, "      aper var req  : %s\n", (v_cfg->prod.flags & PWRFIP_VAR_FLAGS_APER_VAR_REQ) ? "yes" : "no");
            _print(fip, info, "      aper msg req  : %s\n", (v_cfg->prod.flags & PWRFIP_VAR_FLAGS_APER_MSG_REQ) ? "yes" : "no");
        }
    }
    //if (n_cfg->msg.enable)
    //{
    //    _print(fip, info, "msg_cfg             : x%d\n", gw->cfg_msgcnt);
    //    for (i = 0; i < gw->cfg_msgcnt; ++i)
    //    {
    //        struct pwrfip_msg_cfg *m_cfg = &gw->cfg_msglist[i].pwrfip_cfg;
    //        _print(fip, info, "  [%d]\n", i + 1);
    //        _print(fip, info, "    type            : %d (0: recv, 1: send)\n", m_cfg->type);
    //        _print(fip, info, "    src_addr        : 0x%08x \n", m_cfg->hdr.src.addr);
    //        _print(fip, info, "    dst_addr        : 0x%08x \n", m_cfg->hdr.dst.addr);
    //        if (m_cfg->type == PWRFIP_MSG_TYPE_SEND)
    //        {
    //            _print(fip, info, "    channel         : %d (0: aperiodic, 1-8: periodic)\n", m_cfg->tx.channel);
    //            _print(fip, info, "    ack_mode        : %d (0: sdn, 1: sda)\n", m_cfg->tx.ack_mode);
    //        }
    //    }
    //}
    if (n_cfg->ba.enable)
    {
        _print(fip, info, "ba_mcycle_cfg       : x%d\n", gw->fip.mc_cfg_cnt);
        for (i = 0; i < gw->fip.mc_cfg_cnt; ++i)
        {
            struct pwrfip_ba_mcycle_cfg *mc_cfg = &gw->fip.mc_cfglist[i].mc_cfg;
            _print(fip, info, "  [%d] - wind_cnt: x%d\n", i + 1, mc_cfg->wind_cnt);
            for (j = 0; j < mc_cfg->wind_cnt; ++j)
            {
                int k = 0;
                struct pwrfip_ba_wind_cfg *wind_cfg = &gw->fip.mc_cfglist[i].ba_wind_cfglist[j];
                _print(fip, info, "    [%d]\n", j + 1);
                switch (wind_cfg->type)
                {
                    case PWRFIP_BA_WIND_PER:
                        _print(fip, info, "      type          : %d (periodic)\n", wind_cfg->type);
                        _print(fip, info, "      requests      :\n");
                        for (k = 0; k < wind_cfg->per.req_cnt; ++k)
                        {
                            uint8_t req_type = wind_cfg->per.req_list[k].type;
                            uint16_t req_id = wind_cfg->per.req_list[k].id;
                            const char *req_name = (req_type == PWRFIP_BA_ID_DAT) ? "ID_DAT" : "ID_MSG";
                            _print(fip, info, "        [%02d] %s(0x%04x)\n", k + i, req_name, req_id);
                        }
                    break;
                    case PWRFIP_BA_WIND_APER_VAR:
                        _print(fip, info, "      type          : %d (aperiodic var)\n", wind_cfg->type);
                        _print(fip, info, "      end_ustime    : %ld us\n", wind_cfg->aper_var.end_ustime);
                    break;
                    case PWRFIP_BA_WIND_APER_MSG:
                        _print(fip, info, "      type          : %d (aperiodic msg)\n", wind_cfg->type);
                        _print(fip, info, "      end_ustime    : %ld us\n", wind_cfg->aper_msg.end_ustime);
                    break;
                    case PWRFIP_BA_WIND_WAIT:
                        _print(fip, info, "      type          : %d (wait)\n", wind_cfg->type);
                        _print(fip, info, "      end_ustime    : %ld us\n", wind_cfg->wait.end_ustime);
                    break;
                    default: break;
                }
            }
        }
    }
}

const char *bitrate[PWRFIP_BITRATE_RANGE] = {
    "31.25Kbs\0",
    "1Mbps\0",
    "2.5Mbps\0",
    "5Mbps\0",
    "12.5Mbps\0",
    "25Mbps\0",
};

/* turn around time (default) in us, according to the bitrate */
const uint32_t tr_ustime[PWRFIP_BITRATE_RANGE] = {
    424, /* 424us@31.25Kbps */
    30, /* 30us@1Mbps */
    30, /* 30us@2.5Mbps */
    32, /* 32us@5Mbps */
    32, /* 32us@12.5Mbps */
    32, /* 32us@25Mbps */
};

/* silence time (default) in us, according to the bitrate */
const uint32_t ts_ustime[PWRFIP_BITRATE_RANGE] = {
    4096, /* 4096us@31.25Kbps */
    150, /* 150us@1Mbps */
    96, /* 96us@2.5Mbps */
    92, /* 92us@5Mbps */
    92, /* 92us@12.5Mbps */
    92, /* 92us@25Mbps */
};

const char *pwrfip_var_err_str[_PWRFIP_VAR_ERR_MAX] = {
    /* _PWRFIP_VAR_OK */
    "no error",
    /* PWRFIP_VAR_ID_UNKNOWN */
    "unknown var id",
    /* PWRFIP_VAR_NOT_PRODUCING */
    "not a producing var",
    /* PWRFIP_VAR_NOT_CONSUMING */
    "not a consuming var",
    /* PWRFIP_VAR_TX_APER_CH_UNKNOWN */
    "unknown tx aper channel",
    /* PWRFIP_VAR_PDU_INCONSISTENT */
    "inconsistent var pdu",
    /* PWRFIP_VAR_LEN_TOO_LONG */
    "var length too long",
    /* PWRFIP_VAR_LEN_TOO_SHORT */
    "var length too short",
    /* PWRFIP_VAR_NEVER_RECEIVED */
    "var never received",
    /* PWRFIP_VAR_TX_APER_FIFO_EMPTY */
    "tx aper queue is empty",
    /* PWRFIP_VAR_TX_APER_FIFO_FULL */
    "tx aper queue is full",
    /* PWRFIP_VAR_NOT_MEANING */
    "payload not significant",
    /* PWRFIP_VAR_NOT_REFRESH */
    "payload not refreshed",
    /* PWRFIP_VAR_NOT_PROMPT */
    "payload not prompt",
    /* PWRFIP_VAR_BAD_PROMPT_PER */
    "bad reading frequency (promptness)",
    /* PWRFIP_VAR_BAD_REFRESH_PER */
    "bad writing frequency (refreshment)"
};
