/*
 * Created by jiaqiang.ye@lnxall.com
 *
 * Simple GPIO module
 *
 * 2022/05/11
 */

#include <errno.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/types.h>
#include <fcntl.h>
#include <poll.h>
#include <unistd.h>

#include "lnxall_gpio.h"
#include "gpiod.h"
static int def_lnxall_gpio_get(struct lnxall_gpio *gpio);
int def_lnxall_gpio_setval(struct lnxall_gpio *gpio, int val);
static int def_gpio_reopen(struct lnxall_gpio *gpio);
struct gpio_opt
{
    int (*reopen)(struct lnxall_gpio *gpio);
    int (*get)(struct lnxall_gpio *gpio);
    int (*set)(struct lnxall_gpio *gpio, int val);
} gpio_opt = {
    .get = def_lnxall_gpio_get,
    .set = def_lnxall_gpio_setval,
    .reopen = def_gpio_reopen,
};
struct lnxall_gpio * lnxall_gpios_init(int gnum)
{
    int idx;
    struct lnxall_gpio * gpio;
    struct lnxall_gpio * gpios;

    if (gnum <= 0 || gnum > LNXALL_GPIO_MAXNUM) {
        fprintf(stderr, "Error, invalid number of GPIO: %d\n", gnum);
        fflush(stderr);
        return NULL;
    }

    gpios = (struct lnxall_gpio *) calloc(
        (size_t) gnum, sizeof(struct lnxall_gpio));

    for (idx = 0; idx < gnum; ++idx) {
        gpio = &gpios[idx];
        gpio->gpio_num = 0;
        gpio->gpio_fd = -1;
        gpio->gpio_val = 0;
        gpio->isout = 0;
        gpio->changed = 0;
    }
    return gpios;
}

static int def_gpio_reopen(struct lnxall_gpio * gpio)
{
    size_t rl0;
    int gfd, errn;
    char gpath[128];
    const char * dirp;
    
    gfd = gpio->gpio_fd;
    if (gfd != -1) {
        close(gfd);
        gpio->gpio_fd = -1;
    }

    snprintf(gpath, sizeof(gpath), "/sys/class/gpio/gpio%d/direction", gpio->gpio_num);
    if (access(gpath, F_OK) == -1) {
        char gpio_num[24];
        int exfd, glen, error;

        error = 0;
        glen = snprintf(gpio_num, sizeof(gpio_num), "%d\n", gpio->gpio_num);
        exfd = open("/sys/class/gpio/export", O_WRONLY | O_TRUNC);
        if (exfd != -1) {
            if (glen <= 0 || write(exfd, gpio_num, (size_t) glen) != (ssize_t) glen)
                error = 1;
            close(exfd);
        } else
            error = 2;
        if (error || access(gpath, F_OK) == -1) {
            fprintf(stderr, "Error, failed to export GPIO %d: %d\n", gpio->gpio_num, error);
            fflush(stderr);
            return -1;
        }
    }
    gfd = open(gpath, O_WRONLY | O_CLOEXEC | O_TRUNC);
    if (gfd == -1) {
        errn = errno;
        fprintf(stderr, "Error, failed to open(%s): %s\n", gpath, strerror(errn));
        fflush(stderr);
        return -1;
    }

    dirp = gpio->isout ? "out\n" : "in\n";
    rl0 = strlen(dirp);
    if (write(gfd, dirp, rl0) != (ssize_t) rl0) {
        errn = errno;
        close(gfd);
        fprintf(stderr, "Error, failed to set direction for GPIO %d: %s\n",
            gpio->gpio_num, strerror(errn));
        fflush(stderr);
        return -1;
    }
    close(gfd);

    do {
        size_t triglen;
        const char * trig = "both\n";

        if (gpio->isout) {
            /*
             * For GPIO output, set the trigger mode to none
             * Because it will not be polled
             */
            trig = "none\n";
        }
        triglen = strlen(trig);

        /* for GPIO input, trigger at both edges */
        snprintf(gpath, sizeof(gpath), "/sys/class/gpio/gpio%d/edge", gpio->gpio_num);
        gfd = open(gpath, O_WRONLY | O_CLOEXEC | O_TRUNC);
        if (gfd == -1) {
            errn = errno;
            fprintf(stderr, "Error, failed to open(%s): %s\n", gpath, strerror(errn));
            fflush(stderr);
        } else {
            if (write(gfd, trig, triglen) != (ssize_t) triglen) {
                errn = errno;
                fprintf(stderr, "Error, failed to set trigger mode '%s' for GPIO %d: %s\n",
                    trig, gpio->gpio_num, strerror(errn));
                fflush(stderr);
            }
            close(gfd);
        }
    } while (0);

    snprintf(gpath, sizeof(gpath), "/sys/class/gpio/gpio%d/value", gpio->gpio_num);
    gfd = open(gpath, O_RDWR | O_CLOEXEC);
    if (gfd == -1) {
        errn = errno;
        fprintf(stderr, "Error, failed to open(%s): %s\n", gpath, strerror(errn));
        fflush(stderr);
        return -1;
    }
    gpio->gpio_fd = gfd;
    return 0;
}

static int libgpiod_lnxall_gpio_get(struct lnxall_gpio * gpio)
{
    int gbuf[8]={0};
    int gval;

    int gpio_num = gpio->gpio_num;
    char line[64] = {0};
    unsigned int offsets = gpio_num%32;
    snprintf(line, sizeof(line), "%d", gpio_num/32);
    int status = gpiod_simple_get_value_multiple("gpioget", line,
        &offsets, gbuf,
        1, 0);
    if (status < 0)
		perror("error reading GPIO values");
    // printf("GET:%s,%d->%d\n",line,offsets,gbuf[0]);
    gval = -1;
    if (gpio_num/32 == 1 && gpio->isout == 1)
    {
        if((Reversal_flag != 1) )
        {
            if (gbuf[0] == 1) {
                gval = 0;
                gpio->gpio_val = 0;
            } else {
                gval = 1;
                gpio->gpio_val = 1;
            }
        }
        else
        {
            if (gbuf[0] == 0) {
                gval = 0;
                gpio->gpio_val = 0;
            } else {
                gval = 1;
                gpio->gpio_val = 1;
            }
        }
    }
    else
    {
        if((Reversal_flag == 1) )
        {
            if (gbuf[0] == 1) {
                gval = 0;
                gpio->gpio_val = 0;
            } else {
                gval = 1;
                gpio->gpio_val = 1;
            }
        }
        else
        {
            if (gbuf[0] == 0) {
                gval = 0;
                gpio->gpio_val = 0;
            } else {
                gval = 1;
                gpio->gpio_val = 1;
            }
        }
    }  
    
    return gval;
}

static int def_lnxall_gpio_get(struct lnxall_gpio * gpio)
{
    off_t rval;
    ssize_t rl1;
    char gbuf[8];
    int errn, gpfd, gval;

    gpfd = gpio->gpio_fd;
    if (gpfd == -1)
        return -1;
    rval = lseek(gpfd, 0, SEEK_SET);
    if (rval != 0) {
        errn = errno;
        fprintf(stderr, "Error, failed to seek %d: %s\n",
            gpfd, strerror(errn));
        fflush(stderr);
        return -1;
    }

    rl1 = read(gpfd, gbuf, sizeof(gbuf));
    if (rl1 <= 0) {
        errn = errno;
        fprintf(stderr, "Error, failed to read(%d) has returned %d: %s\n",
            gpfd, (int) rl1, strerror(errn));
        fflush(stderr);
        return -1;
    }

    gval = -1;
    if((Reversal_flag == 1) && (gpio->isout == 0))
    {
        if (gbuf[0] == '1') {
            gval = 0;
            gpio->gpio_val = 0;
        } else {
            gval = 1;
            gpio->gpio_val = 1;
        }
    }
    else
    {
        if (gbuf[0] == '0') {
            gval = 0;
            gpio->gpio_val = 0;
        } else {
            gval = 1;
            gpio->gpio_val = 1;
        }
    }
    
    lseek(gpfd, 0, SEEK_SET);
    return gval;
}

int lnxall_gpio_getval(struct lnxall_gpio * gpios, int gnum)
{
    int idx, rval;
    struct lnxall_gpio * gpio;

    rval = 0;
    for (idx = 0; idx < gnum; ++idx) {
        gpio = &gpios[idx];
        if (gpio_opt.get(gpio) < 0)
            rval--;
    }

    return rval;
}

int libgpiod_lnxall_gpio_setval(struct lnxall_gpio * gpio, int val)
{
    int ret, gpio_num;
    int value[1] = {0};

    gpio_num = gpio->gpio_num;
    char line[64] = {0};
    unsigned int offsets = gpio_num%32;

       
    if (gpio_num/32 == 1)
    {
        if(Reversal_flag != 1)
            value[0] = val ? 0 : 1;
        else
            value[0] = val ? 1 : 0;
    }
    else
    {
        if(Reversal_flag == 1)
            value[0] = val ? 0 : 1;
        else
            value[0] = val ? 1 : 0;
    }  
    snprintf(line, sizeof(line), "%d", gpio_num/32);
    ret = gpiod_simple_set_value_multiple("gpioset",line, &offsets, value, 1, 0, NULL, NULL);
    if (ret < 0) 
    {
        printf("Error, failed to set value for GPIO %d\n", gpio->gpio_num);
        return -1;
    }
    printf("Set value for GPIO %d, %d\n", gpio_num, value[0]);
    return 0;
}
int lnxall_gpio_setval(struct lnxall_gpio * gpio, int val)
{
    return gpio_opt.set(gpio, val);
}

int def_lnxall_gpio_setval(struct lnxall_gpio * gpio, int val)
{
    off_t rval;
    ssize_t rl1;
    int errn, gpfd;
    const char * gval;

    gpfd = gpio->gpio_fd;
    if (gpfd == -1)
        return -1;
    rval = lseek(gpfd, 0, SEEK_SET);
    if (rval != 0) {
        errn = errno;
        fprintf(stderr, "Error, failed to seek %d: %s\n",
            gpfd, strerror(errn));
        fflush(stderr);
        return -1;
    }

    gval = val ? "1\n" : "0\n";
    rl1 = write(gpfd, gval, 0x2);
    if (rl1 != 0x2) {
        errn = errno;
        fprintf(stderr, "Error, failed to read(%d) has returned %d: %s\n",
            gpfd, (int) rl1, strerror(errn));
        fflush(stderr);
        return -1;
    }

    gpio->gpio_val = val ? 1 : 0;
    lseek(gpfd, 0, SEEK_SET);
    return 0;
}

int lnxall_gpio_poll(struct lnxall_gpio * gpios, int gnum, int timeo)
{
    int rval;
    int idx, ret, errn;
    struct pollfd * pfd;
    struct pollfd * pfds;
    struct lnxall_gpio * gpio;

    rval = 0;
    if (gnum <= 0 || gnum > LNXALL_GPIO_MAXNUM) {
        fprintf(stderr, "Error, invalid number of GPIO: %d\n", gnum);
        fflush(stderr);
        return -1;
    }

    pfds = (struct pollfd *) calloc((size_t) gnum, sizeof(struct pollfd));
    if (pfds == NULL) {
        fputs("Error, system out of memory!\n", stderr);
        fflush(stderr);
        return -1;
    }
    for (idx = 0; idx < gnum; ++idx) {
        pfd = &pfds[idx];
        gpio = &gpios[idx];
        pfd->fd = gpio->gpio_fd;
        pfd->events = POLLPRI;
        pfd->revents = 0;
    }

    ret = poll(pfds, gnum, timeo);
    if (ret < 0) {
        errn = errno;
        free(pfds);
        if (errn == EINTR)
            return 0;
        fprintf(stderr, "Error, GPIO poll has failed: %s\n", strerror(errn));
        fflush(stderr);
        return -1;
    }

    if (ret == 0) {
        free(pfds);
        return 0; /* timed out */
    }
    for (idx = 0; idx < gnum; ++idx) {
        pfd = &pfds[idx];
        gpio = &gpios[idx];
        if (pfd->revents != 0) {
            gpio->changed = 1;
            rval++;
        }
    }

    free(pfds);
    return rval;
}

int lnxall_gpio_free(struct lnxall_gpio * gpios, int gnum)
{
    int idx;
    struct lnxall_gpio * gpio;

    for (idx = 0; idx < gnum; ++idx) {
        gpio = &gpios[idx];
        if (gpio->gpio_fd != -1) {
            close(gpio->gpio_fd);
            gpio->gpio_fd = -1;
        }
    }

    free(gpios);
    return 0;
}

int lnxall_gpio_open(struct lnxall_gpio * gpios, int gnum)
{
    int ret, idx, count;
    struct lnxall_gpio * gpio;

    count = 0;
    for (idx = 0; idx < gnum; ++idx) {
        gpio = &gpios[idx];
        ret = 1;
        if (gpio_opt.reopen != NULL) {
            gpio_opt.reopen(gpio);
        }
        if (ret < 0) {
            fprintf(stderr, "Error, failed to open GPIO %d!\n", gpio->gpio_num);
            fflush(stderr);
            continue;
        }
        count++;
    }
    return count;
}

int lnxall_gpio_later_handle(struct lnxall_gpio *gpios, int gnum, const char *board_name)
{
    struct lnxall_gpio * gpio = NULL;
    if ((strcasecmp(board_name, WLOS_BAU_B30_003) == 0) || (strcasecmp(board_name, BAU_B30_003) == 0)) 
    {
        for (int idx = 0; idx < gnum; ++idx) {
            gpio = &gpios[idx];
            gpio->gpio_num = 0;
            gpio->gpio_fd = -1;
            gpio->gpio_val = 1;
            gpio->isout = 0;
            gpio->changed = 0;
        }
        gpio_opt.get = libgpiod_lnxall_gpio_get;
        gpio_opt.set = libgpiod_lnxall_gpio_setval;
        gpio_opt.reopen = NULL;
    }
    return 0;
}