#include <zephyr/kernel.h>
#include <zephyr/drivers/gpio.h>
#include <nrfx_timer.h>
#include <nrfx_gpiote.h>
#include <helpers/nrfx_gppi.h>
#include <hal/nrf_gpio.h>

#include <zephyr/logging/log.h>
LOG_MODULE_REGISTER(Logger, LOG_LEVEL_INF);

static const uint32_t quartz_frequency = 16175056;

static const struct gpio_dt_spec btn0 = GPIO_DT_SPEC_GET(DT_ALIAS(sw0), gpios); // P1.13 on nRF54L15DK
uint32_t btn_psel = NRF_DT_GPIOS_TO_PSEL(DT_ALIAS(sw0), gpios);
static nrfx_timer_t timer_inst = NRFX_TIMER_INSTANCE(NRF_TIMER20);
static nrfx_gpiote_t gpiote_inst = NRFX_GPIOTE_INSTANCE(NRF_GPIOTE20);

/* frequency measurement */
static volatile uint32_t edge_count = 0;
static volatile uint32_t total_edges = 0;
static volatile uint32_t total_value = 0;
static volatile bool new_measurement = true;
static volatile bool active = true;

void gpiote_handler(nrfx_gpiote_pin_t pin,
                       nrfx_gpiote_trigger_t trigger,
                       void *p_context)
{
    ARG_UNUSED(p_context);
    ARG_UNUSED(trigger);

    if (pin == btn_psel) {
        if (new_measurement){
            new_measurement = false;
            total_edges = 0;
            total_value = 0;
            active = true;
            nrfx_timer_clear(&timer_inst);
            nrfx_timer_resume(&timer_inst);
        } else if (active){
            edge_count++;
            uint32_t value = nrfx_timer_capture_get(&timer_inst, NRF_TIMER_CC_CHANNEL0);
            if (value > 10000000){
                nrfx_timer_pause(&timer_inst);
                total_value = value;
                total_edges = edge_count;
                edge_count = 0;
                active = false;
            }
        }
    }
}

static void timer_init_timer_mode(void)
{
    nrfx_err_t status;
    uint32_t base_frequency = NRF_TIMER_BASE_FREQUENCY_GET(timer_inst.p_reg);
    nrfx_timer_config_t cfg = NRFX_TIMER_DEFAULT_CONFIG(base_frequency);
    cfg.mode      = NRF_TIMER_MODE_TIMER;       // timer mode
    cfg.bit_width = NRF_TIMER_BIT_WIDTH_32;     // large range
    cfg.p_context = NULL;

    status = nrfx_timer_init(&timer_inst, &cfg, NULL);
    NRFX_ASSERT(status == NRFX_SUCCESS);

    nrfx_timer_clear(&timer_inst);
}

static void gpiote_init_edge_input(uint32_t pin)
{
    nrfx_err_t status;
    uint8_t in_channel;

    // init driver instance
    status = nrfx_gpiote_init(&gpiote_inst, NRFX_GPIOTE_DEFAULT_CONFIG_IRQ_PRIORITY);
    if (status != NRFX_SUCCESS && status != NRFX_ERROR_ALREADY) {
        // handle error
    }
    LOG_INF("GPIOTE status: %s",
                  nrfx_gpiote_init_check(&gpiote_inst) ? "initialized" : "not initialized");

    status = nrfx_gpiote_channel_alloc(&gpiote_inst, &in_channel);
    if (status != NRFX_SUCCESS) {
        // handle error
    }

    static const nrf_gpio_pin_pull_t pull_cfg = NRF_GPIO_PIN_PULLUP;
    nrfx_gpiote_trigger_config_t trig_cfg = {
        .trigger      = NRFX_GPIOTE_TRIGGER_HITOLO,
        .p_in_channel = &in_channel,
    };
    static const nrfx_gpiote_handler_config_t handler_cfg = {
        .handler  = gpiote_handler,
        .p_context = NULL,
    };
    nrfx_gpiote_input_pin_config_t input_cfg = {
        .p_pull_config    = &pull_cfg,
        .p_trigger_config = &trig_cfg,
        .p_handler_config = &handler_cfg,
    };

    status = nrfx_gpiote_input_configure(&gpiote_inst, pin, &input_cfg);
    if (status != NRFX_SUCCESS) {
        // handle error
    }

    nrfx_gpiote_trigger_enable(&gpiote_inst, pin, true);
}

static void connect_edge_to_timer(uint32_t pin)
{
    int status;
    nrfx_gppi_handle_t gppi_handle;

    // Get the event and task addresses
    uint32_t evt_addr = nrfx_gpiote_in_event_address_get(&gpiote_inst, pin);
    uint32_t task_addr = nrfx_timer_task_address_get(&timer_inst, NRF_TIMER_TASK_CAPTURE0);
    // uint32_t task_addr = nrfx_timer_capture_task_address_get(&timer_inst, NRF_TIMER_CC_CHANNEL0);

    status = nrfx_gppi_conn_alloc(evt_addr, task_addr, &gppi_handle);
    NRFX_ASSERT(status == NRFX_SUCCESS);

    // Enable the connection using the handle
    nrfx_gppi_conn_enable(gppi_handle);

    // Enable timer after nrfx_gppi_conn_enable as seen in the example.
    nrfx_timer_enable(&timer_inst);
    LOG_INF("Timer status: %s", nrfx_timer_is_enabled(&timer_inst) ? "enabled" : "disabled");
}

static void sample(void)
{
    if (total_value > 0){
        float freq = 1.0f * quartz_frequency*total_edges/total_value;
        LOG_INF("Edges: %d\tValue: %d\t Freq: %.2f Hz", total_edges, total_value, freq);
    }
    new_measurement = true;
}

int main(void)
{
    if (!gpio_is_ready_dt(&btn0)) {
        return 0;
    }
    // gpio_pin_configure_dt(&btn0, GPIO_INPUT);

#if defined(__ZEPHYR__)
    IRQ_CONNECT(NRFX_IRQ_NUMBER_GET(NRF_TIMER20), IRQ_PRIO_LOWEST,
                nrfx_timer_irq_handler, &timer_inst, 0);
    IRQ_CONNECT(NRFX_IRQ_NUMBER_GET(NRF_GPIOTE20), IRQ_PRIO_LOWEST,
                nrfx_gpiote_irq_handler, &gpiote_inst, 0);
#endif

    timer_init_timer_mode();
    gpiote_init_edge_input(btn_psel);
    connect_edge_to_timer(btn_psel);
    
    new_measurement = true;

    while (1) {
        k_msleep(1000);
        sample();
    }

    return 0;
}
