#include "lora.h"

LOG_MODULE_REGISTER(lora);

struct lorawan_join_config join_cfg; // lorawan config file for initial join

uint8_t dev_eui[] = LORAWAN_DEV_EUI;
uint8_t join_eui[] = LORAWAN_JOIN_EUI;
uint8_t app_key[] = LORAWAN_APP_KEY;

static void lorwan_datarate_changed(enum lorawan_datarate dr)
{
	uint8_t unused, max_size;

	lorawan_get_payload_sizes(&unused, &max_size);
	LOG_INF("New Datarate: DR_%d, Max Payload %d", dr, max_size);
}

static void dl_callback(uint8_t port, bool data_pending, int16_t rssi, int8_t snr, uint8_t len, const uint8_t *data)
{
	LOG_INF("Port %d, Pending %d, RSSI %ddB, SNR %ddBm", port, data_pending, rssi, snr);
	if (data) {
		LOG_HEXDUMP_INF(data, len, "Payload: ");
	}
}

struct lorawan_downlink_cb downlink_cb = {
		.port = LW_RECV_PORT_ANY,
		.cb = dl_callback
};

uint8_t battery_level_cb(void){
    // Read battery voltage from npm1300
    uint16_t v_bat_adc = npm1300_measure_battery_voltage(I2C1_BUS);
    // Convert ADC value (0-1023) to range 0-254 (corresponding to 0V-5V)
    uint8_t battery_level = (uint8_t)((uint32_t)(v_bat_adc)*254/1023);
    // TO DO: 0 and 255 have special meanings, handle them properly
    //return battery_level;
    return 111; // Placeholder value for testing
};

int lorawan_set_single_channel(uint8_t channel)
{
    if (channel >= 8) {
        LOG_ERR("Channel %d out of range (0..7)", channel);
        return -EINVAL;
    }

    // Build channels mask: one uint16_t element (channels 0..7) for EU868
    uint16_t channels_mask[LORAWAN_CHANNELS_MASK_SIZE_EU868] = { 0 };

    // Enable only the specified channel
    channels_mask[0] = 1 << channel;

    return lorawan_set_channels_mask(channels_mask, LORAWAN_CHANNELS_MASK_SIZE_EU868);
}

/**
 * @brief Initalizes LoRa-Wan communication
 * @param lora_dev pointer to the lora device from the alias list  
 * @param dr the desired datarate (spreading factor)
 * @param channel the uplink channel to use
 * @retval 0 if successul, otherwise the error code provided by the function that failed
 */
int lorawan_init(const struct device* lora_dev, enum lorawan_datarate dr, uint8_t channel)
{
    // Check if device is ready
    if (!device_is_ready(lora_dev)) {
        LOG_ERR("LoRa device not ready: %s", lora_dev->name);
        return 1;
    }
    // start the lora stack
    int ret = lorawan_start();
    if (ret) {
        LOG_ERR("lorawan_start failed: %d", ret);
        return ret;
    }
    // set the region to EU868
    ret = lorawan_set_region(LORAWAN_REGION_EU868);
    if (ret) {
        LOG_ERR("Failed to set region: %d", ret);
        return ret;
    }
    // Force uplink to use a single channel
    ret = lorawan_set_single_channel(channel);
    if (ret) {
        LOG_ERR("Failed to set single channel: %d", ret);
        return ret;
    }
    // set the device class to Class A
    ret = lorawan_set_class(LORAWAN_CLASS_A);
    if (ret) {
        LOG_ERR("Failed to set device class: %d", ret);
        return ret;
    }
    // Set the spreading factor (data rate) to SF10 (DR2)
    lorawan_enable_adr(false); // Disable Adaptive Data Rate
    ret = lorawan_set_datarate(dr);  // Set data rate. DR_2 → SF10, 125kHz Bandwidth
    if (ret) {
        LOG_ERR("Failed to set datarate: %d", ret);
        return ret;
    }

    lorawan_register_downlink_callback(&downlink_cb); // For receiving downlink messages, not used currently
	lorawan_register_dr_changed_callback(lorwan_datarate_changed); // From SDK example, not sure what it does
    // Register battery level callback: currently has no effect, as the server side must request battery level (?)
    lorawan_register_battery_level_callback(battery_level_cb); // Register battery level callback

    LOG_INF("LoRaWAN initialized");
    return 0;
}

int lorawan_join_network(void)
{
    LOG_INF("lorawan_join_network called.");
    int ret = 0;

    join_cfg.mode      = LORAWAN_ACT_OTAA;
    join_cfg.dev_eui   = dev_eui;
    join_cfg.otaa.join_eui = join_eui;
    join_cfg.otaa.app_key  = app_key;
    join_cfg.otaa.nwk_key  = app_key;

    // TO DO: What to do if we get stuck trying to join the network for a long time? reboot?
    do {
        LOG_INF("---------------Trying to join--------------------");
        ret = lorawan_join(&join_cfg);
        if (ret == -ETIMEDOUT) {
            LOG_ERR("Join failed due to timeout: %d", ret);
        } else if(ret != 0) {
            LOG_ERR("Join failed: %d", ret);
        } else {
            LOG_INF("Join successful");
        }
    } while (ret != 0);


    return ret;
}

/**
 * @brief Send LoRa Data
 * @param payload pointer to uint8_t array containing the data to be sent 
 * @retval 0 if successully sent, otherwise the error code provided by lorawan_send
 */
int lorawan_send_data(uint8_t *payload, uint8_t size){
    int ret = lorawan_send(
        2,           // FPort
        payload,
        size,
        LORAWAN_MSG_UNCONFIRMED // gateway should not confirm the message. (alternative: LORAWAN_MSG_CONFIRMED)
    );
	if (ret != 0) {
			LOG_ERR("lorawan_send failed: %d.", ret);
		} 
    return ret; 
}

