/*
 * 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();

static const char *pwrfip_var_err_str[_PWRFIP_VAR_ERR_MAX];

int fip_start(struct gateway *gw)
{
    int i;
    const struct pwrfip_version *lib_version = pwrfip_version_get();
    struct pwrfip_node_cfg node_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;

    memset(&node_cfg, 0, sizeof(struct pwrfip_node_cfg));

    /* pwrfip - versions */
    _print(app, info,
        "mb-fip gateway: v%s - modbus lib: v%d.%d.%d - fip lib: v%d.%d.%d\n",
        APP_VERSION,
        libmodbus_version_major, libmodbus_version_minor, libmodbus_version_micro,
        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 */
    node_cfg.fip_phy_addr = gw->fip.addr;
    node_cfg.fip_seg_num = gw->fip.seg;
    node_cfg.fip_frm_type = gw->fip.frm_type;
    node_cfg.fip_bitrate = gw->fip.bitrate;
    node_cfg.turn_around_ustime = gw->fip.tr_ustime;
    node_cfg.silence_ustime = gw->fip.ts_ustime;
    node_cfg.enable_bimedium = 1;

    /* node identification configuration */
    node_cfg.ident.manufacturer_name = "Exoligent\0";
    node_cfg.ident.model_name = "Modbus <-> FIP gateway\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 = gw->fip.dev; /* attach pci device to node configuration */

    /* app handlers */
    node_cfg.pwrfip_var_handler = NULL;
    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 = NULL;
    node_cfg.pwrfip_ba_sync_handler = NULL;
    node_cfg.pwrfip_error_handler = fip_error_handler;
    node_cfg.pwrfip_reset_handler = fip_reset_handler;

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

    gw->fip.n = pwrfip_node_init(&node_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->fip.cfg_varcnt; ++i) {
        struct pwrfip_var_cfg *var_cfg = &gw->fip.cfg_varlist[i];
        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;
        }
    }

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

    return 0;

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;

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

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

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

    fd_status = report.medium_status.state;
    printf("  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");
    printf("  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;
    printf("  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);
    printf("               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;
    printf("  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);
    printf("               ok=%"PRIu64"\n", report.tx_err.ok);
}

void fip_id_sync_handler(struct pwrfip_node *node,
        struct pwrfip_var *var, struct pwrfip_event *event)
{
    struct gateway *gw = (struct gateway *) node->infos->cfg.user_ctx;
    int i;
    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);

    gw->mbus.mapping_locked = 1; /* TODO: lock semaphore */

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

        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 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(fip, warn, "r_var[0x%04x] state: %s\n",
                    var->id, err_str);
                gw->stats.r_var_warn++;
            }
            continue;
        }
        /* update modbus value from fip */
        memcpy(&gw->mbus.mapping->tab_input_registers[gw->mbus.cons_varlist[i].addr],
            var->buffer, var->bsz);

        gw->stats.r_var_ok++;
    }

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

        /* update fip value from modbus */
        memcpy(var->buffer,
            &gw->mbus.mapping->tab_registers[gw->mbus.cons_varlist[i].addr],
            var->bsz);

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

    gw->mbus.mapping_locked = 0; /* TODO: unlock semaphore */

    _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, &gw->stats.min_sync_cb_last,
            &gw->stats.max_sync_cb_last);
    /* => event treatment duration */
    update_stats(&start_treatment, &stop_treatment, &gw->stats.nb_sync_cb,
        &gw->stats.cumul_sync_cb_duration,
        &gw->stats.min_sync_cb_duration,
        &gw->stats.max_sync_cb_duration);
}

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

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

static 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)"
};