#include "RadiolibTactilityHal.h"

#include "hal/gpio_hal.h"
#include "esp_timer.h"

constexpr const char* TAG = "RadiolibTactilityHal";

void RadiolibTactilityHal::init() {
    // we only need to init the SPI here
    spiBegin();
}

void RadiolibTactilityHal::term() {
    // we only need to stop the SPI here
    spiEnd();
}

void RadiolibTactilityHal::pinMode(uint32_t pin, uint32_t mode) {
    if(pin == RADIOLIB_NC) {
        return;
    }

    gpio_hal_context_t gpiohal;
    gpiohal.dev = GPIO_LL_GET_HW(GPIO_PORT_0);

    gpio_config_t conf = {
        .pin_bit_mask = (1ULL<<pin),
        .mode = (gpio_mode_t)mode,
        .pull_up_en = GPIO_PULLUP_DISABLE,
        .pull_down_en = GPIO_PULLDOWN_DISABLE,
        .intr_type = (gpio_int_type_t)gpiohal.dev->pin[pin].int_type,
    };
    gpio_config(&conf);
}

void RadiolibTactilityHal::digitalWrite(uint32_t pin, uint32_t value) {
    if(pin == RADIOLIB_NC) {
        return;
    }

    gpio_set_level((gpio_num_t)pin, value);
}

uint32_t RadiolibTactilityHal::digitalRead(uint32_t pin) {
    if(pin == RADIOLIB_NC) {
        return(0);
    }

    return(gpio_get_level((gpio_num_t)pin));
}

void RadiolibTactilityHal::attachInterrupt(uint32_t interruptNum, void (*interruptCb)(void), uint32_t mode) {
    if(interruptNum == RADIOLIB_NC) {
        return;
    }

    gpio_install_isr_service((int)ESP_INTR_FLAG_IRAM);
    gpio_set_intr_type((gpio_num_t)interruptNum, (gpio_int_type_t)(mode & 0x7));

    // this uses function typecasting, which is not defined when the functions have different signatures
    // untested and might not work
    gpio_isr_handler_add((gpio_num_t)interruptNum, (void (*)(void*))interruptCb, NULL);
}

void RadiolibTactilityHal::detachInterrupt(uint32_t interruptNum) {
    if(interruptNum == RADIOLIB_NC) {
        return;
    }

    gpio_isr_handler_remove((gpio_num_t)interruptNum);
    gpio_wakeup_disable((gpio_num_t)interruptNum);
    gpio_set_intr_type((gpio_num_t)interruptNum, GPIO_INTR_DISABLE);
}

void RadiolibTactilityHal::delay(unsigned long ms) {
    vTaskDelay(ms / portTICK_PERIOD_MS);
}

void RadiolibTactilityHal::delayMicroseconds(unsigned long us) {
    uint64_t m = (uint64_t)esp_timer_get_time();
    if(us) {
        uint64_t e = (m + us);
        if(m > e) { // overflow
            while((uint64_t)esp_timer_get_time() > e);
        }
        while((uint64_t)esp_timer_get_time() < e);
    }
}

unsigned long RadiolibTactilityHal::millis() {
    return((unsigned long)(esp_timer_get_time() / 1000ULL));
}

unsigned long RadiolibTactilityHal::micros() {
    return((unsigned long)(esp_timer_get_time()));
}

long RadiolibTactilityHal::pulseIn(uint32_t pin, uint32_t state, unsigned long timeout) {
    if(pin == RADIOLIB_NC) {
        return(0);
    }

    this->pinMode(pin, GPIO_MODE_INPUT);
    uint32_t start = this->micros();
    uint32_t curtick = this->micros();

    while(this->digitalRead(pin) == state) {
        if((this->micros() - curtick) > timeout) {
            return(0);
        }
    }

    return(this->micros() - start);
}

void RadiolibTactilityHal::spiBegin() {
    if (!spiInitialized) {
        TT_LOG_I(TAG, "SPI Begin!");
        spi_device_interface_config_t devcfg = {};
        devcfg.clock_speed_hz = spiFrequency;
        devcfg.mode = 0;
        devcfg.spics_io_num = csPin;
        devcfg.queue_size = 1;
        esp_err_t ret = spi_bus_add_device(spiHostDevice, &devcfg, &spiDeviceHandle);
        if (ret != ESP_OK) {
            TT_LOG_E(TAG, "Failed to add SPI device: %s", esp_err_to_name(ret));
        }
        spiInitialized = true;
    }
}

void RadiolibTactilityHal::spiBeginTransaction() {
    // This function is used to set up the transaction (speed, bit order, mode, ...).
    // With the ESP-IDF HAL this is automatically done, so no code needed.
}

void RadiolibTactilityHal::spiTransfer(uint8_t* out, size_t len, uint8_t* in) {
    spi_transaction_t t;

    auto lock = getLock()->asScopedLock();
    bool locked = lock.lock(portMAX_DELAY);
    if (!locked) {
        TT_LOG_E(TAG, "Failed to aquire SPI lock");
    }

    memset(&t, 0, sizeof(t));  // Zero out the transaction
    t.length = len * 8;        // Length is in bits
    t.tx_buffer = out;         // The data to send
    t.rx_buffer = in;          // The data to receive
    spi_device_polling_transmit(spiDeviceHandle, &t);
}

void RadiolibTactilityHal::spiEndTransaction() {
    // nothing needs to be done here
}

void RadiolibTactilityHal::spiEnd() {
    if (spiInitialized) {
        spi_bus_remove_device(spiDeviceHandle);
        spiInitialized = false;
    }
}
