/*
 * All Rights Reserved (C) 2021-2025
 * Exoligent SARL (Colombes, France)
 * Website: www.exoligent.com
 * Author : Maxime Coroyer <maxime.coroyer@exoligent.com>
 *
 * Description:
 * Ping test between two FIP nodes using PowerFIP API
 *
 *              PING                WORLDFIP              PONG
 *   /-------------------------\                /------------------------\
 *   |      Master [0x00]      |                |       Slave [0xXY]     |
 *   +-------------------------+                +------------------------+
 *   |                         |                |                        |
 *   | (w_cnt++) PROD [0x05XY] +--------------->| CONS [0x05XY] ------+  |
 *   |  ^                      |                |                     |  |
 *   |  |                      |                |                     |  |
 *   |  | op:cmp(p_var,c_var)  |                | op:cpy(p_var,c_var) |  |
 *   |  |                      |                |                     |  |
 *   |  :                      |                |                     :  |
 *   |  +(r_cnt) CONS [0x06XY] |<---------------+ PROD [0x06XY] <-----+  |
 *   |                         |                |                        |
 *   \-------------------------/                \------------------------/
 *
 */

#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>     // memset()
#include <stdint.h>     // uint8_t, ...
#include <fcntl.h>      // open()
#include <unistd.h>     // read()
#include <libgen.h>     // basename
#include <inttypes.h>   // PRIx64+
#include <signal.h>     // signal
#include <time.h>       // nanosleep, timespec
#if defined (_WIN32) || defined (_WIN64)
#include <conio.h>      // kbhit()
#endif

#include "common.h"

char *app_name = NULL;
int running = 0;

static void help();
void handle_signal(int sig);

int main(int argc, char *argv[])
{
    int ret = 0;
    int dev_idx = 1;
    int ismstr_opt = 0;
    int ping_addr_opt = 1;
    int varlen_opt = 125;
    int bitrate_opt = 1;
    uint32_t tr_opt = 0;
    uint32_t ts_opt = 0;
    int cycle_ustime_opt = 5000; /* 5ms */
    int value = 0;
    struct timespec rqtp = {0};
    struct timespec rmtp = {0};
    struct tst_session sess;
    char *erase_end_line;

#if defined (_WIN32) || defined (_WIN64)
    SetPriorityClass(GetCurrentProcess(), REALTIME_PRIORITY_CLASS);
    enable_console_colorization();
#else
    uint8_t buff = 0;
#endif

    app_name = basename(argv[0]);

    /* Try to process all command line arguments */
    while ((value = getopt(argc, argv, "i:ma:d:c:b:r:s:lhv")) != -1) {
        switch (value) {
            case 'i':
                sscanf(optarg, "%d", &dev_idx);
                break;
            case 'm':
                ismstr_opt = 1;
                break;
            case 'a':
                sscanf(optarg, "%d", &ping_addr_opt);
                if ((ping_addr_opt < 1) || (ping_addr_opt > 255)) {
                    help();
                    return EXIT_FAILURE;
                }
                break;
            case 'd':
                sscanf(optarg, "%d", &varlen_opt);
                if ((varlen_opt < 1) || (varlen_opt > 125)) {
                    help();
                    return EXIT_FAILURE;
                }
                break;
            case 'c':
                sscanf(optarg, "%d", &cycle_ustime_opt);
                if ((cycle_ustime_opt < 500) || (cycle_ustime_opt > 500000)) {
                    help();
                    return EXIT_FAILURE;
                }
                break;
            case 'b':
                sscanf(optarg, "%d", &bitrate_opt);
                if (bitrate_opt >= (_PWRFIP_BITRATE_MAX - 1)) {
                    help();
                    return EXIT_FAILURE;
                }
                break;
            case 'r':
                sscanf(optarg, "%u", &tr_opt);
                break;
            case 's':
                sscanf(optarg, "%u", &ts_opt);
                break;
            case 'l':
                dev_show_all();
                return EXIT_SUCCESS;
            case 'h':
                help();
                return EXIT_SUCCESS;
            case 'v':
                printf("%s %s\n", app_name, APP_VERSION);
                return EXIT_SUCCESS;
            case '?':
            default:
                help();
                return EXIT_FAILURE;
        }
    }

    signal(SIGINT, handle_signal);

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

    /* exoligent - fip device opening */
    memset(&sess, 0, sizeof(struct tst_session));
    if ((ret = dev_open(dev_idx, &sess)))
        goto fail1;

    sess.ping_addr = ping_addr_opt;
    sess.varlen = varlen_opt;
    sess.station_ismstr = ismstr_opt;
    sess.station_number = (ismstr_opt) ? 0 : ping_addr_opt;
    sess.station_bitrate = bitrate_opt;
    sess.station_tr_ustime = tr_opt;
    sess.station_ts_ustime = ts_opt;
    sess.ba_cycle_ustime = cycle_ustime_opt;
    if (tst_session_start(&sess) < 0) {
        _print(app, err, "impossible to start fip station %d.\n", sess.station_number);
        goto fail2;
    }

    _print(app, info, "app started. press enter to close...\n");
    erase_end_line = (dbg_coloriz) ? "\033[0K" : "";

#if defined(__linux__) || defined(__QNX__)
    /* set stdin non-blocking */
    fcntl(STDIN_FILENO, F_SETFL, O_NONBLOCK);
#endif

    rqtp.tv_sec = 0;
    rqtp.tv_nsec = 250000000; /* 250ms */

    /* main loop */
    running = 1;
    while (running) {

        /* wait for a pressed key */
#if defined (_WIN32) || defined (_WIN64)
        if (kbhit())
#else
        if (read(STDIN_FILENO, &buff, 1) == 1)
#endif
            running = 0;

        if (sess.station_ismstr) {
            _print(sess.station_number, info,
                "=> ping/pong: ok=%"PRIu64" nok=%"PRIu64"%s\r",
                sess.stats.pp_ok, sess.stats.pp_nok, erase_end_line);
            fflush(stdout);
        }

        ret = nanosleep(&rqtp, &rmtp);
    }
    printf("\n");

    if (tst_session_stop(&sess))
        _print(app, err, "impossible to stop fip station %d.\n", sess.station_number);

fail2:
    /* exoligent - fip device closure */
    if (dev_close(&sess))
        _print(app, err, "cannot close correctly the device.\n");
fail1:
    pwrfip_exit();
    _print(app, info, "app closed\n");
    return ret;
}

/**
 * \brief Callback function for handling signals.
 * \param sig identifier of signal
 */
void handle_signal(int sig)
{
    if (sig == SIGINT) {
        _print(app, info, "SIGINT signal received\n");
        running = 0;
        /* Reset signal handling to default behavior */
        signal(SIGINT, SIG_DFL);
    } else
        _print(app, warn, "signal received but not supported (sig=%d)\n", sig);
}

/**
 * \brief Print help for this application
 */
static void help()
{
    printf("\n");
    printf("Usage: %s [OPTION]...\n\n", app_name);
    printf("It tests ping/pong periodic variable between two FIP nodes.\n");
    printf("By default, if no option is added, the app opens the PCI/PCIe device\n");
    printf("with index 1 [-i 1].\n\n");
    printf("Options:\n");
    printf("    -i   device index [default=1]\n");
    printf("    -m   enable master (ping mode) else slave (pong mode) [default=disabled]\n");
    printf("    -a   ping address [default=1]\n");
    printf("         slave  case: local address\n");
    printf("         master case: remote address to ping\n");
    printf("                      [note: local address for master is always 0]\n");
    printf("    -d   data length for 0x05xy and 0x06xy [default=125]\n");
    printf("    -c   cycle time in microseconds [min=500,max=500000] [default=5000]\n");
    printf("         [note: this setting is also used to configure refreshment/promptness]\n");
    printf("    -b   bitrate [default=1]\n");
    printf("         0: 31.25Kbps\n");
    printf("         1: 1Mbps\n");
    printf("         2: 2.5Mbps\n");
    printf("         3: 5Mbps\n");
    printf("         4: 12.5Mbps\n");
    printf("         5: 25Mbps\n");
    printf("    -r   turn-around time in us [default=0]\n");
    printf("         default TR times:\n");
    printf("           @31.25Kbps: 424us\n");
    printf("           @1Mbps    : 30us\n");
    printf("           @2.5Mbps  : 30us\n");
    printf("           @5Mbps    : 32us\n");
    printf("           @12.5Mbps : 32us\n");
    printf("           @25Mbps   : 32us\n");
    printf("    -s   silence time in us [default=0]\n");
    printf("         default TS times:\n");
    printf("           @31.25Kbps: 4096us\n");
    printf("           @1Mbps    : 150us\n");
    printf("           @2.5Mbps  : 96us\n");
    printf("           @5Mbps    : 92us\n");
    printf("           @12.5Mbps : 92us\n");
    printf("           @25Mbps   : 92us\n");
    printf("    -l   list the FIP boards present on the host machine\n");
    printf("    -h   show this help and exit\n");
    printf("    -v   show version and exit\n");
    printf("\n");
    printf("Examples: \n");
    printf("    ping mode: master node (addr=0x00) pings slave node (addr=0x01)\n");
    printf("    %s -i 1 -m -a 1 -c 1000\n", app_name);
    printf("    pong mode: slave node (addr=0x01)\n");
    printf("    %s -i 2 -a 1 -c 1000\n", app_name);
    printf("\n");
}
