/*
 * Copyright (C) MOXA Inc. All rights reserved.
 * Authors:
 *     2024  Wilson YS Huang  <wilsonys.huang@moxa.com>
 * This software is distributed under the terms of the MOXA SOFTWARE NOTICE.
 * See the file LICENSE for details.
 */

#define _GNU_SOURCE
#include <dirent.h>
#include <fcntl.h>
#include <limits.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/wait.h>
#include <time.h>
#include <unistd.h>

#include <common.h>
#define SYSFS_DMI_BASEPATH "/sys/class/dmi/id"
#define READ_END           0
#define WRITE_END          1
#define BUFFER_SIZE        1024

enum {
    GPIO_VALUE_LOW  = 0,
    GPIO_VALUE_HIGH = 1
};

#define SYSFS_GPIO_BASEPATH      "/sys/class/gpio"
#define SYSFS_GPIO_EXPORT_PATH   SYSFS_GPIO_BASEPATH "/export"
#define SYSFS_GPIO_UNEXPORT_PATH SYSFS_GPIO_BASEPATH "/unexport"

void sleep_ms(int32_t milliseconds)
{
#if _POSIX_C_SOURCE >= 199309L
    struct timespec ts;
    ts.tv_sec  = milliseconds / 1000;
    ts.tv_nsec = (milliseconds % 1000) * 1000000;
    nanosleep(&ts, NULL);
#else
    usleep(milliseconds * 1000);
#endif
}

bool get_sysfs_dmi_value(const char *attribute, char *buffer)
{
    int32_t fd;
    int32_t rc;
    char   *attr_path;

    rc = asprintf(&attr_path, SYSFS_DMI_BASEPATH "/%s", attribute);
    if (rc < 0) {
        return false;
    }

    fd = open(attr_path, O_RDONLY);
    if (fd < 0) {
        return false;
    }

    rc = read(fd, buffer, 64);
    if (rc < 0) {
        return false;
    }

    buffer[strcspn(buffer, "\n")] = 0;

    free(attr_path);
    close(fd);

    return true;
}

static bool execute(char *file[], int input_fd, int output_fd)
{
    if (file == NULL || file[0] == NULL) {
        return false;
    }

    if (input_fd != STDIN_FILENO) {
        dup2(input_fd, STDIN_FILENO);
        close(input_fd);
    }
    if (output_fd != STDOUT_FILENO) {
        dup2(output_fd, STDOUT_FILENO);
        close(output_fd);
    }

    if (execvp(file[0], file) == -1) {
        return false;
    }

    return true;
}

bool execute_pipeline(char **commands[], int32_t num_commands, char *output_buff)
{
    int32_t pipe_fds[2];
    int32_t input_fd = STDIN_FILENO;
    ssize_t bytes_read;

    for (int32_t i = 0; i < num_commands; i++) {
        if (pipe(pipe_fds) == -1) {
            return false;
        }

        pid_t pid = fork();
        if (pid == -1) {
            return false;
        }

        if (pid == 0) {
            close(pipe_fds[READ_END]);
            if (!execute(commands[i], input_fd, pipe_fds[WRITE_END])) {
                return false;
            }
        }
        else {
            if (i < num_commands - 1) {
                close(pipe_fds[WRITE_END]);
            }

            if (input_fd != STDIN_FILENO) {
                close(input_fd);
            }

            if (i < num_commands - 1) {
                input_fd = pipe_fds[READ_END];
            }

            wait(NULL);
        }
    }

    if (output_buff != NULL) {
        bytes_read = read(pipe_fds[READ_END], output_buff, BUFFER_SIZE - 1);
        if (bytes_read == -1) {
            return false;
        }

        output_buff[bytes_read] = '\0';
    }

    close(pipe_fds[WRITE_END]);
    close(pipe_fds[READ_END]);

    return true;
}

static bool check_and_export_gpio(int32_t gpio)
{
    char    file_path[PATH_MAX];
    char    buffer[4];
    int32_t fd;

    snprintf(file_path, PATH_MAX, SYSFS_GPIO_BASEPATH "/gpio%d/value", gpio);
    if (access(file_path, F_OK) < 0) {
        snprintf(buffer, 4, "%d", gpio);
        fd = open(SYSFS_GPIO_EXPORT_PATH, O_WRONLY);
        if (fd < 0) {
            return false;
        }

        if (write(fd, buffer, sizeof(buffer)) < 0) {
            close(fd);
            return false;
        }

        close(fd);
    }

    return true;
}

bool get_gpio_value(int32_t gpio, int32_t *value)
{
    char    file_path[PATH_MAX];
    char    buffer[2] = {0};
    int32_t fd;

    if (!check_and_export_gpio(gpio)) {
        return false;
    }

    snprintf(file_path, PATH_MAX, SYSFS_GPIO_BASEPATH "/gpio%d/value", gpio);

    fd = open(file_path, O_RDONLY);
    if (fd < 0) {
        return false;
    }

    if (read(fd, buffer, sizeof(buffer) - 1) <= 0) {
        close(fd);
        return false;
    }

    close(fd);
    buffer[1] = '\0';

    if (!strncmp(buffer, "0", 1)) {
        *value = GPIO_VALUE_LOW;
    }
    else if (!strncmp(buffer, "1", 1)) {
        *value = GPIO_VALUE_HIGH;
    }
    else {
        return false;
    }

    return true;
}

bool get_gpio_base_by_label(const char *target_label, int32_t *gpio_base)
{
    DIR           *dir;
    struct dirent *entry;
    char           label_path[PATH_MAX];
    char           base_path[PATH_MAX];
    char           label[256];
    FILE          *fp;
    FILE          *base_fp;
    int32_t        base;

    dir = opendir(SYSFS_GPIO_BASEPATH);
    if (!dir) {
        return false;
    }

    while ((entry = readdir(dir)) != NULL) {
        if (strncmp(entry->d_name, "gpiochip", 8) != 0) {
            continue;
        }

        snprintf(label_path, sizeof(label_path), "%s/%s/label", SYSFS_GPIO_BASEPATH, entry->d_name);

        fp = fopen(label_path, "r");
        if (!fp) {
            continue;
        }

        if (!fgets(label, sizeof(label), fp)) {
            fclose(fp);
            continue;
        }
        fclose(fp);

        label[strcspn(label, "\n")] = 0;

        if (strcmp(label, target_label) != 0) {
            continue;
        }

        snprintf(base_path, sizeof(base_path), "%s/%s/base", SYSFS_GPIO_BASEPATH, entry->d_name);

        base_fp = fopen(base_path, "r");
        if (!base_fp) {
            continue;
        }

        if (fscanf(base_fp, "%d", &base) != 1) {
            fclose(base_fp);
            continue;
        }
        fclose(base_fp);
        closedir(dir);

        *gpio_base = base;
        return true;
    }

    closedir(dir);
    return false;
}
