#include <stdio.h>
#include <gpiod.h>

#include <pin_control.h>

// https://docs.kernel.org/driver-api/gpio/using-gpio.html

#define GPIO_CHIP "gpiochip0"

struct gpiod_chip *chip;

int init_gpio() {
    cleanup_gpio();

    // https://libgpiod-dlang.dpldocs.info/gpiod.gpiod_chip_open_by_name.html
    chip = gpiod_chip_open_by_name(GPIO_CHIP);
    if (!chip) {
        perror("[pin_control] gpiod_chip_open_by_name(\"" GPIO_CHIP "\")");
        return -1;
    }

    return 0;
}

void cleanup_gpio() {
    // https://libgpiod-dlang.dpldocs.info/gpiod.gpiod_chip_close.html
    if (chip) {
        gpiod_chip_close(chip);
        chip = NULL;
    }
}

static int set_pin_state(int pin, int state) {
    int ret = -1;
    if (!chip) {
        perror("[pin_control] attempt to set GPIO line for invalid chip");
        goto exit;
    }

    // https://libgpiod-dlang.dpldocs.info/gpiod.gpiod_chip_get_line.html
    struct gpiod_line *line = gpiod_chip_get_line(chip, pin);   // this may need an offset
    if (!line) {
        perror("[pin_control] gpiod_chip_get_line()");
        goto exit;
    }

    // https://libgpiod-dlang.dpldocs.info/gpiod.gpiod_line_request_output.html
    if (gpiod_line_request_output(line, "house-leds", state) < 0) {
        perror("[pin_control] gpiod_line_request_output()");
        goto exit_line;
    }

    // https://libgpiod-dlang.dpldocs.info/gpiod.gpiod_line_set_value.html
    if (gpiod_line_set_value(line, state) < 0) {
        perror("[pin_control] gpiod_line_set_value()");
        goto exit_line;
    }

    ret = 0;

exit_line:
    // https://libgpiod-dlang.dpldocs.info/gpiod.gpiod_line_release.html
    gpiod_line_release(line);
exit:
    return ret;
}

int turn_on_pin(int pin) {
    return set_pin_state(pin, 1);
}

int turn_off_pin(int pin) {
    return set_pin_state(pin, 0);
}

int probe_pin(int pin) {
    int ret = -1;
    if (!chip) {
        perror("[pin_control] attempt to read GPIO line for invalid chip");
        goto exit;
    }

    struct gpiod_line *line = gpiod_chip_get_line(chip, pin);
    if (!line) {
        perror("[pin_control] gpiod_chip_get_line()");
        goto exit;
    } else if (gpiod_line_request_input(line, "house-leds") < 0) {
        perror("[pin_control] gpiod_line_request_input()");
        goto exit_line;
    }

    ret = gpiod_line_get_value(line);
    if (ret < 0) {
        perror("[pin_control] gpiod_line_get_value()");
        goto exit_line;
    }

exit_line:
    // https://libgpiod-dlang.dpldocs.info/gpiod.gpiod_line_release.html
    gpiod_line_release(line);
exit:
    return ret;
}

int turn_on_all_pins() {
    for (int pin = 0; pin <= 5; pin++)
        if (turn_on_pin(pin) < 0)
            return -1;

    return 0;
}

int turn_off_all_pins() {
    for (int pin = 0; pin <= 5; pin++) {
        if (turn_off_pin(pin) < 0)
            return -1;
    }
    return 0;
}

static int button_presssed(int event, unsigned int pin, const struct timespec * timestamp, void *data) {
    (void) event;
    (void) timestamp;
    * (int *) data = (int) pin;
    return GPIOD_CTXLESS_EVENT_POLL_RET_STOP;
}

int wait_for_button_press(const unsigned pins[static 5]) {
    // https://www.lane-fu.com/linuxmirror/libgpiod/doc/html/group______high__level____.html#ga3ac28eb59bbd31b8b2298f76047d377d
    
    int pressed_pin = -1;

    struct timespec ts = {
        .tv_sec = 3600,
        .tv_nsec = 0,
    };

    int ret = gpiod_ctxless_event_monitor_multiple(
        GPIO_CHIP,
        GPIOD_CTXLESS_EVENT_FALLING_EDGE,
        pins,
        5,
        true,
        "house-leds",
        &ts,     // big timout
        NULL,
        button_presssed,
        &pressed_pin);

    if (ret < 0) {
        perror("[pin_control] gpiod_ctxless_event_monitor_multiple()");
        return -1;
    }

    return pressed_pin;
}
