#include "sysfs_gpio.h"
#include <errno.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include "dy_utils/dy_common.h"

static const char *GPIO_DIR_FMT = "/sys/class/gpio/gpio%d";
static const char *GPIO_EXPORT = "/sys/class/gpio/export";


static int make_gpio_available(int ngpio)
{
    struct stat ss;
    char gpio_dir[32], nstr[16];
    int nlen, fd;

    sprintf(gpio_dir, GPIO_DIR_FMT, ngpio);
    nlen = sprintf(nstr, "%d", ngpio);

    if(stat(gpio_dir, &ss) == 0) {
        return 0;
    }

    if(errno != ENOENT) {
        dy_syslog(LOG_ERR, "%s", gpio_dir);
        return -1;
    }

    fd = open(GPIO_EXPORT, O_WRONLY);
    if(fd == -1) {
        dy_syslog(LOG_ERR, "%s", GPIO_EXPORT);
        return -1;
    }

    if(write(fd, nstr, nlen) != nlen) {
        close(fd);
        dy_syslog(LOG_ERR, "%s", GPIO_EXPORT);
        return -1;
    }

    close(fd);

    if(stat(gpio_dir, &ss) == 0) {
        return 0;
    }

    return -1;
}

sysfs_gpio *sysfs_gpio_open(int ngpio)
{
    sysfs_gpio *fs_gpio;
    char dirname[32];

    if(make_gpio_available(ngpio) != 0) {
        return NULL;
    }

    fs_gpio = calloc(1, sizeof(sysfs_gpio));
    fs_gpio->ngpio = ngpio;
    fs_gpio->dir_fd = fs_gpio->value_fd = fs_gpio->direction_fd = -1;

    sprintf(dirname, GPIO_DIR_FMT, ngpio);
    fs_gpio->dir_fd = open(dirname, O_RDONLY);
    if(-1 == fs_gpio->dir_fd) {
        dy_syslog(LOG_ERR, "%s", dirname);
        free(fs_gpio);
        return NULL;
    }

    return fs_gpio;
}

void sysfs_gpio_close(sysfs_gpio *fs_gpio)
{
    if(fs_gpio) {
        if(fs_gpio->dir_fd != -1)
            close(fs_gpio->dir_fd);
        if(fs_gpio->value_fd != -1)
            close(fs_gpio->value_fd);
        if(fs_gpio->direction_fd != -1)
            close(fs_gpio->direction_fd);
        free(fs_gpio);
    }
}

int sysfs_gpio_set_direction(sysfs_gpio *fs_gpio, int inout)
{
    static const char *words[2]={ "in", "out" };
    static const int   len[2]={ 2, 3 };

    inout = !!inout; /* only 0 or 1 */

    if(fs_gpio->direction_fd == -1) {
        fs_gpio->direction_fd = openat(fs_gpio->dir_fd, "direction", O_RDWR);
        dy_syslog(LOG_INFO, "Opened gpio%d direction as fd %d.\n", fs_gpio->ngpio, fs_gpio->direction_fd);
    }
    if(fs_gpio->direction_fd == -1) {
        dy_syslog(LOG_ERR, "direction (for gpio %d)\n", fs_gpio->ngpio);
        return -1;
    }

    if(write(fs_gpio->direction_fd, words[inout], len[inout]) != len[inout]) {
        dy_syslog(LOG_ERR, "direction\n");
        return -1;
    }

    return 0;
}

int sysfs_gpio_set_value(sysfs_gpio *fs_gpio, int value)
{
    char out_val = '0' + !!value;  /* '0' or '1' */

    if(fs_gpio->value_fd == -1) {
        fs_gpio->value_fd = openat(fs_gpio->dir_fd, "value", O_RDWR);
        dy_syslog(LOG_INFO, "Opened gpio%d value as fd %d.\n", fs_gpio->ngpio, fs_gpio->value_fd);
    }
    if(fs_gpio->value_fd == -1) {
        dy_syslog(LOG_ERR, "value (for gpio %d)\n", fs_gpio->ngpio);
        return -1;
    }

    if(write(fs_gpio->value_fd, &out_val, 1) != 1) {
        dy_syslog(LOG_ERR, "write()");
        return -1;
    }

    return 0;
}

int sysfs_gpio_get_value(sysfs_gpio *fs_gpio, int *value)
{
    char out_val;

    if(fs_gpio->value_fd == -1) {
        fs_gpio->value_fd = openat(fs_gpio->dir_fd, "value", O_RDWR);
        dy_syslog(LOG_ERR, "Opened gpio%d value as fd %d.\n", fs_gpio->ngpio, fs_gpio->value_fd);
    }
    if(fs_gpio->value_fd == -1) {
        dy_syslog(LOG_ERR, "value (for gpio %d)\n", fs_gpio->ngpio);
        return -1;
    }
    if(read(fs_gpio->value_fd, &out_val, 1) != 1) {
        dy_syslog(LOG_ERR, "read()");
        return -1;
    }

    if(out_val == '0')
        *value = 0;
    if(out_val == '1')
        *value = 1;

    return 0;
}