/*
 * Created by jiaqiang.ye@lnxall.com
 *
 * GPIO input/output as modbus TCP
 *
 * 2022/05/12
 */

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
#include <signal.h>

#include "dido-modbus.h"
#include "dy_common.h"

struct dido_modbus * dido_modbus_create(int num_output, int num_input)
{
    int gnum, ret;
    const char * mbver;
    struct dido_modbus * pdm;

    pdm = NULL;
    gnum = num_output + num_input;
    if (gnum <= 0 || num_output < 0 || num_output > WOOLINK_DO_MAX ||
        num_input < 0 || num_input > WOOLINK_DI_MAX) {
        fprintf(stderr, "Error, invalid GPIO numbers: %d, %d\n",
            num_output, num_input);
        fflush(stderr);
        return pdm;
    }

    pdm = (struct dido_modbus *) calloc(0x1, sizeof(*pdm));
    if (pdm == NULL) {
err0:
        fputs("Error, system out of memory!\n", stderr);
        fflush(stderr);
        return pdm;
    }

    pdm->dm_num_output = num_output;
    pdm->dm_num_input = num_input;
    pdm->dm_sock = -1;

    pdm->dm_gpios = lnxall_gpios_init(gnum);
    if (pdm->dm_gpios == NULL) {
        free(pdm);
        pdm = NULL;
        goto err0;
    }

    ret = pthread_mutex_init(&pdm->dm_lock, NULL);
    if (ret != 0) {
        lnxall_gpio_free(pdm->dm_gpios, gnum);
        pdm->dm_gpios = NULL;
        free(pdm); pdm = NULL;
        fprintf(stderr, "Error, failed to initialize mutex: %d\n", ret);
        fflush(stderr);
        return pdm;
    }

    mbver = getenv("DIDO_MODBUS_VERBOSE");
    if (mbver && mbver[0] == '1')
        pdm->dm_verbose = 1;

    return pdm;
}

static const char * dido_modbus_boardname(char * bnstr, size_t bnlen)
{
    ssize_t rl1;
    int bfd, errn;
    const char * bnfile = BOARD_NAME_PATH;

    if (bnlen <= 0x1)
        return NULL;

    bfd = open(bnfile, O_RDONLY | O_CLOEXEC);
    if (bfd == -1) {
        errn = errno;
        fprintf(stderr, "Error, failed to open(%s): %s\n", bnfile, strerror(errn));
        fflush(stderr);
        bnstr[0] = '\0';
        return NULL;
    }

    rl1 = read(bfd, bnstr, bnlen - 1);
    if (rl1 <= 0) {
        fprintf(stderr, "Error, cannot get boardname: %d\n", (int) rl1);
        fflush(stderr);
        close(bfd);
        return NULL;
    }

    close(bfd); bfd = -1;
    while (rl1 > 0) {
        char cha;
        cha = bnstr[rl1 - 1];
        if (cha != ' ' && cha != '\t' && cha != '\r' && cha != '\n')
            break;
        rl1--;
        bnstr[rl1] = '\0';
    }

    bnstr[rl1] = '\0';
    if (bnstr[0] != '\0')
        return bnstr;
    return NULL;
}

int dido_modbus_gpio_get(int * p_out, int * p_in)
{
    char bname[64];

    *p_in = 0;
    *p_out = 0;
    if (dido_modbus_boardname(bname, sizeof(bname)) == NULL) {
        fputs("Error, unknown board!\n", stderr);
        fflush(stderr);
        return -1;
    }

    if (strcasecmp(bname, BOARD_WOOLink2F6280_W_4G) == 0) {
        *p_out = 0x8;
        *p_in = 0x10;
    } else if (strcasecmp(bname, WLOSANY_EM1000) == 0) {
        *p_out = 8;
        *p_in = 18;
    } else if ((strcasecmp(bname, WLOS_BAU_B30_003) == 0) || (strcasecmp(bname, BAU_B30_003) == 0)) {
        *p_out = 8;
        *p_in = 4;
    } else
    {
        fprintf(stderr, "Unsupported board for DIDO_ModBus_TCP: %s\n", bname);
        fflush(stderr);
        return -1;
    }
    return 0;
}

int dido_modbus_opengpio(struct dido_modbus * dido)
{
    char bname[64];
    int idx, ret, gnum;
    struct lnxall_gpio * gpio;

    if (dido_modbus_boardname(bname, sizeof(bname)) == NULL)
        return -1;

    gpio = dido->dm_gpios;
    if (strcasecmp(bname, BOARD_WOOLink2F6280_W_4G) == 0) {
        idx = -1;
        /* 8 GPIO output Pins: DO */
        idx++; gpio[idx].isout = 1; gpio[idx].gpio_num = 124; /* DO_1 */
        idx++; gpio[idx].isout = 1; gpio[idx].gpio_num = 122; /* DO_2 */
        idx++; gpio[idx].isout = 1; gpio[idx].gpio_num = 121; /* DO_3 */
        idx++; gpio[idx].isout = 1; gpio[idx].gpio_num = 118; /* DO_4 */
        idx++; gpio[idx].isout = 1; gpio[idx].gpio_num = 117; /* DO_5 */
        idx++; gpio[idx].isout = 1; gpio[idx].gpio_num = 119; /* DO_6 */
        idx++; gpio[idx].isout = 1; gpio[idx].gpio_num = 120; /* DO_7 */
        idx++; gpio[idx].isout = 1; gpio[idx].gpio_num = 123; /* DO_8 */

        /* 16 GPIO input Pins: DI */
        idx++; gpio[idx].gpio_num = 81; /* DI_01 */
        idx++; gpio[idx].gpio_num = 79; /* DI_02 */
        idx++; gpio[idx].gpio_num = 77; /* DI_03 */
        idx++; gpio[idx].gpio_num = 75; /* DI_04 */
        idx++; gpio[idx].gpio_num = 73; /* DI_05 */
        idx++; gpio[idx].gpio_num = 71; /* DI_06 */
        idx++; gpio[idx].gpio_num = 69; /* DI_07 */
        idx++; gpio[idx].gpio_num = 65; /* DI_08 */
        idx++; gpio[idx].gpio_num = 66; /* DI_09 */
        idx++; gpio[idx].gpio_num = 68; /* DI_10 */
        idx++; gpio[idx].gpio_num = 78; /* DI_11 */
        idx++; gpio[idx].gpio_num = 76; /* DI_12 */
        idx++; gpio[idx].gpio_num = 72; /* DI_13 */
        idx++; gpio[idx].gpio_num = 70; /* DI_14 */
        idx++; gpio[idx].gpio_num = 67; /* DI_15 */
        idx++; gpio[idx].gpio_num = 64; /* DI_16 */
    } else if (strcasecmp(bname, WLOSANY_EM1000) == 0) {
        idx = -1;
        /* 8 GPIO output Pins: DO */
        idx++; gpio[idx].isout = 1; gpio[idx].gpio_num = 436; /* DO_1 */
        idx++; gpio[idx].isout = 1; gpio[idx].gpio_num = 437; /* DO_2 */
        idx++; gpio[idx].isout = 1; gpio[idx].gpio_num = 438; /* DO_3 */
        idx++; gpio[idx].isout = 1; gpio[idx].gpio_num = 439; /* DO_4 */
        idx++; gpio[idx].isout = 1; gpio[idx].gpio_num = 440; /* DO_5 */
        idx++; gpio[idx].isout = 1; gpio[idx].gpio_num = 442; /* DO_6 */
        idx++; gpio[idx].isout = 1; gpio[idx].gpio_num = 447; /* DO_7 */
        idx++; gpio[idx].isout = 1; gpio[idx].gpio_num = 448; /* DO_8 */

        /* 18 GPIO input Pins: DI */
        idx++; gpio[idx].gpio_num = 291; /* DI_01 */
        idx++; gpio[idx].gpio_num = 468; /* DI_02 */
        idx++; gpio[idx].gpio_num = 471; /* DI_03 */
        idx++; gpio[idx].gpio_num = 469; /* DI_04 */
        idx++; gpio[idx].gpio_num = 470; /* DI_05 */
        idx++; gpio[idx].gpio_num = 286; /* DI_06 */
        idx++; gpio[idx].gpio_num = 298; /* DI_07 */
        idx++; gpio[idx].gpio_num = 297; /* DI_08 */
        idx++; gpio[idx].gpio_num = 380; /* DI_09 */
        idx++; gpio[idx].gpio_num = 381; /* DI_10 */
        idx++; gpio[idx].gpio_num = 382; /* DI_11 */
        idx++; gpio[idx].gpio_num = 383; /* DI_12 */
        idx++; gpio[idx].gpio_num = 384; /* DI_13 */
        idx++; gpio[idx].gpio_num = 385; /* DI_14 */
        idx++; gpio[idx].gpio_num = 386; /* DI_15 */
        idx++; gpio[idx].gpio_num = 387; /* DI_16 */
        idx++; gpio[idx].gpio_num = 388; /* DI_17 */
        idx++; gpio[idx].gpio_num = 389; /* DI_18 */
    } else if ((strcasecmp(bname, WLOS_BAU_B30_003) == 0) || (strcasecmp(bname, BAU_B30_003) == 0)) {
        idx = -1;
        /* 8 GPIO output Pins: DO */
        idx++; gpio[idx].isout = 1; gpio[idx].gpio_num = 74; /* DO_13 */
        idx++; gpio[idx].isout = 1; gpio[idx].gpio_num = 73; /* DO_14 */
        idx++; gpio[idx].isout = 1; gpio[idx].gpio_num = 98; /* DO_15 */
        idx++; gpio[idx].isout = 1; gpio[idx].gpio_num = 99; /* DO_16 */
        idx++; gpio[idx].isout = 1; gpio[idx].gpio_num = 40; /* HSD1 */
        idx++; gpio[idx].isout = 1; gpio[idx].gpio_num = 41; /* HSD2 */
        idx++; gpio[idx].isout = 1; gpio[idx].gpio_num = 36; /* HSD3 */
        idx++; gpio[idx].isout = 1; gpio[idx].gpio_num = 42; /* HSD4 */

        /* 18 GPIO input Pins: DI */
        idx++; gpio[idx].gpio_num = 58; /* DI_15 */
        idx++; gpio[idx].gpio_num = 59; /* DI_16 */
        idx++; gpio[idx].gpio_num = 140; /* DI_17 */
        idx++; gpio[idx].gpio_num = 141; /* DI_18 */
    } else {
        return -2;
    }

    gnum = dido->dm_num_output + dido->dm_num_input;
    lnxall_gpio_later_handle(gpio, gnum, bname);
    ret = lnxall_gpio_open(gpio, gnum);
    if (ret < 0)
        return -3;
    /* read gpio values */
    lnxall_gpio_getval(gpio, gnum);
    return 0;
}

int dido_modbus_opentcp(struct dido_modbus * dido, const char * hostaddr, int portno)
{
    dido->dm_ctx = modbus_new_tcp(hostaddr, portno);
    dido->dm_map = modbus_mapping_new(WOOLINK_DIDO_MAX, 0, WOOLINK_DIDO_MAX, 0);

    if (dido->dm_ctx == NULL || dido->dm_map == NULL) {
        fprintf(stderr, "Error, failed to initialize modbus context or data-mapping: %p, %p\n",
            dido->dm_ctx, dido->dm_map);
        fflush(stderr);
        return -1;
    }

    dido->dm_sock = modbus_tcp_listen(dido->dm_ctx, NB_CONNECTION);
    if (dido->dm_sock == -1) {
        fputs("Error, failed to get listening socket!\n", stderr);
        fflush(stderr);
        return -1;
    }

    return 0;
}

void dido_modbus_free(struct dido_modbus * dido)
{
    int ret;
    if (dido->dm_thread != (pthread_t) 0ul) {
        void * retval = NULL;
        pthread_kill(dido->dm_thread, SIGINT);
        pthread_join(dido->dm_thread, &retval);
        dido->dm_thread = (pthread_t) 0ul;
    }

    ret = pthread_mutex_destroy(&dido->dm_lock);
    if (ret != 0) {
        fprintf(stderr, "Error, failed to destroy mutex lock: %d\n", ret);
        fflush(stderr);
    }

    if (dido->dm_sock != -1) {
        close(dido->dm_sock);
        dido->dm_sock = -1;
    }

    if (dido->dm_ctx != NULL) {
        modbus_free(dido->dm_ctx);
        dido->dm_ctx = NULL;
    }

    if (dido->dm_map != NULL) {
        modbus_mapping_free(dido->dm_map);
        dido->dm_map = NULL;
    }

    if (dido->dm_gpios != NULL) {
        int gnum;
        gnum = dido->dm_num_output + dido->dm_num_input;
        lnxall_gpio_free(dido->dm_gpios, gnum);
        dido->dm_gpios = NULL;
        dido->dm_num_input = 0;
        dido->dm_num_output = 0;
    }
    free(dido);
}
