// (most of) these functions are called in sx126x.c


#include "sx126x_hal_stm32.h"
#include <string.h>

#define SX126X_HAL_TIMEOUT_MS 1000U
#define SX126X_GET_STATUS_OPCODE 0xC0U // 0xC0 is SX1262 get status code
#define SX126X_NOP_BYTE          0x00U // dummy byte to len(tx) == len(rx)

static sx126x_hal_status_t sx126x_wait_busy(
    const sx126x_stm32_context_t *radio,
    uint32_t timeout_ms)
/*

    called like sx126x_wait_busy(&my_radio, 100);

    Suppose the my_radio structure is stored at memory address 0x20000100.
    The expression &my_radio returns that address.

    const sx126x_stm32_context_t radio
        tells the compiler that radio points to an sx126x_stm32_context_t
        structure. This allows access to busy_port, busy_pin

    If radio pointer were declared as uint32_t, the compiler would treat the data at
    that address as a uint32_t values instead of as an sx126x_stm32_context_t structure.

    The const qualifier prevents this function from modifying the context
    structure through radio. Attempting to assign to a member through radio
    should produce a compiler error.
 */
{
    uint32_t start = HAL_GetTick();

    // keep checking radio.busy_port 
    // LOW means not busy, ready to accept a new command.

    while (HAL_GPIO_ReadPin(radio->busy_port, radio->busy_pin) == GPIO_PIN_SET)
    {
        if ((HAL_GetTick() - start) >= timeout_ms){return SX126X_HAL_STATUS_ERROR;}
    }
    return SX126X_HAL_STATUS_OK;
}

sx126x_hal_status_t sx126x_hal_reset(const void *context)
/*
    void * makes context a generic pointer, compiler does not know what type of data lives
    at that address

    const because we are not changing values thru this pointer.

    (const sx126x_stm32_context_t *) to cast context to a sx126x_stm32_context_t struct
    This cast does not verify the real type at runtime. The caller must
    ensure that context actually points to an sx126x_stm32_context_t.
    i think (sx126x_stm32_context_t *)context would compile, const just makes it cleaner
*/
{
    const sx126x_stm32_context_t *radio = (const sx126x_stm32_context_t *)context;

    if (radio == NULL){return SX126X_HAL_STATUS_ERROR;}

    // set nss HIGH, disable SPI to radio
    HAL_GPIO_WritePin(
        radio->nss_port,
        radio->nss_pin,
        GPIO_PIN_SET);

    // set reset LOW, LOW holds restart
    HAL_GPIO_WritePin(
        radio->reset_port,
        radio->reset_pin,
        GPIO_PIN_RESET);

    HAL_Delay(2);

    // release restart
    HAL_GPIO_WritePin(
        radio->reset_port,
        radio->reset_pin,
        GPIO_PIN_SET);

    HAL_Delay(10);

    return sx126x_wait_busy(radio, SX126X_HAL_TIMEOUT_MS);
}

sx126x_hal_status_t sx126x_hal_wakeup(const void *context)
{
    const sx126x_stm32_context_t *radio = (const sx126x_stm32_context_t *)context;

    // Byte 2 is a dummy/NOP byte that generates the clocks for the radio to put data on the MISO line
    // datasheet says to do this
    uint8_t command[2] = {SX126X_GET_STATUS_OPCODE, SX126X_NOP_BYTE};

    if (radio == NULL) {return SX126X_HAL_STATUS_ERROR;}

    // nss (chip select is low, we are writing and SPI`ing to the module)
    HAL_GPIO_WritePin(radio->nss_port,radio->nss_pin,GPIO_PIN_RESET);

    HAL_StatusTypeDef status = HAL_SPI_Transmit(radio->spi,command,sizeof(command),SX126X_HAL_TIMEOUT_MS);

    HAL_GPIO_WritePin(radio->nss_port,radio->nss_pin,GPIO_PIN_SET);

    if (status != HAL_OK) {return SX126X_HAL_STATUS_ERROR;}

    return sx126x_wait_busy(radio, SX126X_HAL_TIMEOUT_MS);
}

sx126x_hal_status_t sx126x_hal_write(
    const void *context,
    const uint8_t *command,
    const uint16_t command_length,
    const uint8_t *data,
    const uint16_t data_length)
    // generic write function, its used everywhere in sx126x.c
    // dont mess this up
{
    const sx126x_stm32_context_t *radio = (const sx126x_stm32_context_t *)context;

    if ((radio == NULL) ||(command == NULL) ||(command_length == 0U)){return SX126X_HAL_STATUS_ERROR;}

    if (sx126x_wait_busy(radio, SX126X_HAL_TIMEOUT_MS) != SX126X_HAL_STATUS_OK){return SX126X_HAL_STATUS_ERROR;}

    HAL_GPIO_WritePin(radio->nss_port,radio->nss_pin,GPIO_PIN_RESET);

    HAL_StatusTypeDef status = HAL_SPI_Transmit(
        radio->spi,
        (uint8_t *)command,
        command_length,
        SX126X_HAL_TIMEOUT_MS);

    if ((status == HAL_OK) &&(data != NULL) &&(data_length > 0U))
    {
        status = HAL_SPI_Transmit(
            radio->spi,
            (uint8_t *)data,
            data_length,
            SX126X_HAL_TIMEOUT_MS);
    }

    HAL_GPIO_WritePin(radio->nss_port,radio->nss_pin,GPIO_PIN_SET);

    if (status != HAL_OK){return SX126X_HAL_STATUS_ERROR;}

    return sx126x_wait_busy(radio, SX126X_HAL_TIMEOUT_MS);
}

sx126x_hal_status_t sx126x_hal_read(
    const void *context,
    const uint8_t *command,
    const uint16_t command_length,
    uint8_t *data,
    const uint16_t data_length)
    // generic read function, its used everywhere in sx126x.c
    // dont mess this up
{
    const sx126x_stm32_context_t *radio = (const sx126x_stm32_context_t *)context;


     // // TODO: remove the 64-byte HAL read limit so LoRa RX payloads up to 255
     // // 255 bytes is large for the stack, A better implementation reads in small chunks

    uint8_t dummy_tx[64]; // small buffer for now, could be made up to 255

    if ((radio == NULL) ||
        (radio->spi == NULL) ||
        (command == NULL) ||
        (command_length == 0U) ||
        (data == NULL) ||
        (data_length == 0U) ||
        (data_length > sizeof(dummy_tx)))
    {
        return SX126X_HAL_STATUS_ERROR;
    }

    if (sx126x_wait_busy(radio, SX126X_HAL_TIMEOUT_MS) !=SX126X_HAL_STATUS_OK){return SX126X_HAL_STATUS_ERROR;}

    // fill the first data_length bytes of dummy_tx with SX126X_NOP_BYTE
    memset(dummy_tx, SX126X_NOP_BYTE, data_length);

    HAL_GPIO_WritePin(radio->nss_port,radio->nss_pin,GPIO_PIN_RESET);

    // send single command, usually one byte
    HAL_StatusTypeDef status = HAL_SPI_Transmit(
        radio->spi,
        (uint8_t *)command,
        command_length,
        SX126X_HAL_TIMEOUT_MS);

    // Generate clocks with dummy MOSI bytes while storing the
    // SX1262 response received on MISO.
    // MOSI MISO trasnfer data at the same time, we are not waiting for MOSI
    if (status == HAL_OK)
    {
        status = HAL_SPI_TransmitReceive(
            radio->spi,
            dummy_tx,
            data,
            data_length,
            SX126X_HAL_TIMEOUT_MS);
    }

    HAL_GPIO_WritePin(radio->nss_port,radio->nss_pin,GPIO_PIN_SET);

    if (status != HAL_OK){return SX126X_HAL_STATUS_ERROR;}

    return sx126x_wait_busy(radio, SX126X_HAL_TIMEOUT_MS);
}