/**
 * @file sensors.cpp
 * @author Radim Buráň (xburan03@stud.fit.vutbr.cz)
 */

#include "sensors.hpp"

cJSON* jsonData = NULL;

sensorsData_t* sensorsData = NULL;

extern EventGroupHandle_t STATUS_GROUP;

/**
 * @brief Reads air temperature and humidity
 * 
 * @param arg jsonData
 */
void sensor_sht31(void *arg) {
    cJSON* json = (cJSON*) arg;

    sht3x_t* dev_sht31 = (sht3x_t*)calloc(1, sizeof(sht3x_t));
    sht3x_init_desc(dev_sht31, 0x44, 0, I2C_SDA_PIN, I2C_SCL_PIN);
    sht3x_init(dev_sht31);

    float temperature, humidity;
    sht3x_measure(dev_sht31, &temperature, &humidity);
    ESP_LOGI(LOG_TAG, "SHT3x Sensor: %.2f °C, %.2f %%\n", temperature, humidity);

    cJSON_AddNumberToObject(json, KEY_JSON_OUT_AIR_TEMPERATURE, temperature);
    cJSON_AddNumberToObject(json, KEY_JSON_OUT_AIR_HUMIDITY, humidity);

    xEventGroupSetBits(STATUS_GROUP, BIT_SHT31);

    sht3x_free_desc(dev_sht31);

    vTaskDelete(NULL);
}

/**
 * @brief Gets max value from AS7341 reading, from both groups
 * 
 * @param data1 group 1
 * @param data2 group 2
 * @return uint16_t max value
 */
uint16_t maxAS7341value(DFRobot_AS7341::sModeOneData_t data1, DFRobot_AS7341::sModeTwoData_t data2) {
    uint16_t max = 0;
    if (data1.ADF1 > max) { max = data1.ADF1; }
    if (data1.ADF2 > max) { max = data1.ADF2; }
    if (data1.ADF3 > max) { max = data1.ADF3; }
    if (data1.ADF4 > max) { max = data1.ADF4; }
    if (data2.ADF5 > max) { max = data2.ADF5; }
    if (data2.ADF6 > max) { max = data2.ADF6; }
    if (data2.ADF7 > max) { max = data2.ADF7; }
    if (data2.ADF8 > max) { max = data2.ADF8; }
    return max;
}

/**
 * @brief Get the Gain multiplier based on AGAIN
 * 
 * @param gain AGAIN
 * @return float multiplier
 */
float getGainMulti(uint8_t gain) {
    gain = 10 - gain;
    if (gain == 0) {
        return 0.5f;
    }
    return pow(2, (float)(gain - 1));
}

/**
 * @brief Reads spectral data
 * 
 * @param arg jsonData
 */
void sensor_as7341(void *arg) {
    cJSON* json = (cJSON*) arg;
    ESP_LOGI(LOG_TAG, "AS7341 start");

    DFRobot_AS7341* as7341 = NULL;
    as7341 = new DFRobot_AS7341(0x39, 0, I2C_SDA_PIN, I2C_SCL_PIN);
    as7341->begin();

    DFRobot_AS7341::sModeOneData_t data1;
    DFRobot_AS7341::sModeTwoData_t data2;

    uint8_t gainValue = 9;   //default is 9 = ADC 256x -> x1.0 val

    bool stableReading = false;
    bool stuckAtEdge = false;

    //iterate to find decent result - not saturated channels and not low values
    while (!stableReading && !stuckAtEdge) {
        as7341->setAGAIN(gainValue);

        as7341->startMeasure(as7341->eF1F4ClearNIR);
        data1 = as7341->readSpectralDataOne();

        as7341->startMeasure(as7341->eF5F8ClearNIR);
        data2 = as7341->readSpectralDataTwo();

        uint16_t maxValue = maxAS7341value(data1, data2);

        float gainMulti = getGainMulti(gainValue);

        ESP_LOGI(LOG_TAG, "> Gain: %" PRIu8 " %.1f", gainValue, gainMulti);

        if (maxValue > 950) {
            if (gainValue > 0) {
                --gainValue;
            }
            else {
                stuckAtEdge = true;
            }
        }
        else if (maxValue < 250) {
            if (gainValue < 10) {
                ++gainValue;
            }
            else {
                stuckAtEdge = true;
            }
        }
        else {
            stableReading = true;
        }

        if (!stuckAtEdge && !stableReading) {   //wait only if it's not a last iteration
            vTaskDelay(70 / portTICK_PERIOD_MS);
        }
        else {
            sensorsData->F1 = (uint32_t)(data1.ADF1 * gainMulti);
            sensorsData->F2 = (uint32_t)(data1.ADF2 * gainMulti);
            sensorsData->F3 = (uint32_t)(data1.ADF3 * gainMulti);
            sensorsData->F4 = (uint32_t)(data1.ADF4 * gainMulti);
            sensorsData->F5 = (uint32_t)(data2.ADF5 * gainMulti);
            sensorsData->F6 = (uint32_t)(data2.ADF6 * gainMulti);
            sensorsData->F7 = (uint32_t)(data2.ADF7 * gainMulti);
            sensorsData->F8 = (uint32_t)(data2.ADF8 * gainMulti);
        }
    }

    ESP_LOGI(LOG_TAG, "> F1: %" PRIu32 " [%" PRIu16 "]", sensorsData->F1, data1.ADF1);
    ESP_LOGI(LOG_TAG, "> F2: %" PRIu32 " [%" PRIu16 "]", sensorsData->F2, data1.ADF2);
    ESP_LOGI(LOG_TAG, "> F3: %" PRIu32 " [%" PRIu16 "]", sensorsData->F3, data1.ADF3);
    ESP_LOGI(LOG_TAG, "> F4: %" PRIu32 " [%" PRIu16 "]", sensorsData->F4, data1.ADF4);
    ESP_LOGI(LOG_TAG, "> F5: %" PRIu32 " [%" PRIu16 "]", sensorsData->F5, data2.ADF5);
    ESP_LOGI(LOG_TAG, "> F6: %" PRIu32 " [%" PRIu16 "]", sensorsData->F6, data2.ADF6);
    ESP_LOGI(LOG_TAG, "> F7: %" PRIu32 " [%" PRIu16 "]", sensorsData->F7, data2.ADF7);
    ESP_LOGI(LOG_TAG, "> F8: %" PRIu32 " [%" PRIu16 "]", sensorsData->F8, data2.ADF8);

    char buf[64];
    sprintf(buf, "%" PRIu32 ";%" PRIu32 ";%" PRIu32 ";%" PRIu32 ";%" PRIu32 ";%" PRIu32 ";%" PRIu32 ";%" PRIu32, sensorsData->F1, sensorsData->F2, sensorsData->F3, sensorsData->F4, sensorsData->F5, sensorsData->F6, sensorsData->F7, sensorsData->F8);

    cJSON_AddStringToObject(json, KEY_JSON_OUT_LIGHT, buf);
    
    xEventGroupSetBits(STATUS_GROUP, BIT_AS7341);

    vTaskDelete(NULL);
}

/**
 * @brief Reads data from vl53L0x sensor - water level
 * 
 * @param arg jsonData
 */
void sensor_vl53l0x(void *arg) {
    cJSON* json = (cJSON*) arg;
    uint8_t val = 0;

    VL53L0X* lox = new VL53L0X(0x29, 0, I2C_SDA_PIN, I2C_SCL_PIN);
    if (lox->init()) {
        ESP_LOGI("VL53L0X", "Init finished");
        val = lox->readRangeSingleMillimeters();        
        ESP_LOGI(LOG_TAG, "Value: %d", val);
    }
    else {
        ESP_LOGI("VL53L0X", "Init failed...");
    }

    val = map(val, WATER_TANK_LEVEL_MAX, WATER_TANK_LEVEL_MIN, 100, 0);

    sensorsData->waterTankLevel = val;

    cJSON_AddNumberToObject(json, KEY_JSON_OUT_WATER_LEVEL, val);

    xEventGroupSetBits(STATUS_GROUP, BIT_VL53L0X);

    vTaskDelete(NULL);
}

/**
 * @brief Reads data from soil sensor - soil humidity
 * 
 * @param arg jsonData
 */
void sensor_soil(void *arg) {
    cJSON* json = (cJSON*) arg;

    esp_adc_cal_characteristics_t adc1_chars;

    esp_adc_cal_characterize(ADC_UNIT_1, ADC_ATTEN_DB_11, ADC_WIDTH_BIT_12, 0, &adc1_chars);
    adc1_config_width(ADC_WIDTH_BIT_12);
    adc1_config_channel_atten(ADC1_CHANNEL_7, ADC_ATTEN_DB_11);

    uint32_t val = 0;
    val = esp_adc_cal_raw_to_voltage(adc1_get_raw(ADC1_CHANNEL_7), &adc1_chars);
    ESP_LOGI(LOG_TAG, "Voltage: %ld", val);

    uint8_t res = map(val, 945, 2500, 100, 0);

    sensorsData->soilHumidity = res;

    cJSON_AddNumberToObject(json, KEY_JSON_OUT_SOIL_HUMIDITY, res);

    xEventGroupSetBits(STATUS_GROUP, BIT_SOIL);

    vTaskDelete(NULL);
}

/**
 * @brief Runs after all sensors are done. Starts actuators
 * 
 * @param arg jsonData
 */
void sensorsFinished(void *arg) {
    cJSON* json = (cJSON*) arg;

    while (1) {
        EventBits_t ebits = xEventGroupWaitBits(STATUS_GROUP, BIT_ALL, false, false, portMAX_DELAY);

        if ((ebits & BIT_WIFI_MQTT_SENSORS) == BIT_WIFI_MQTT_SENSORS) {
            xEventGroupClearBits(STATUS_GROUP, ebits);
            ESP_LOGI(LOG_TAG, "Sensors finished | WiFi OK | MQTT OK | GOT PRESET ... sending!");

            mqttPublish(MQTT_TOPIC_SENSORS, cJSON_Print(json), 0);
            cJSON_Delete(json);

            activateActuators(sensorsData, true);
        }
        else if ((ebits & BIT_OFFLINE_SENSORS_FINISHED) == BIT_OFFLINE_SENSORS_FINISHED) {
            xEventGroupClearBits(STATUS_GROUP, ebits);
            ESP_LOGI(LOG_TAG, "Offline sensors finished - ");
            cJSON_Delete(json);
            activateActuators(sensorsData, false);
        }
        else if ((ebits & BIT_ACTUATORS_FINISHED) == BIT_ACTUATORS_FINISHED) {
            xEventGroupClearBits(STATUS_GROUP, ebits);
            vTaskDelay(500 / portTICK_PERIOD_MS);
            goToSleep();
        }
    }
}

/**
 * @brief Starts all sensors. After finish, will start actuators
 * 
 * @param onlineMode if device is connected to the WiFi
 */
void sensorsStart(bool onlineMode) {
    i2cdev_init();

    jsonData = createJsonTemplate();
    sensorsData = (sensorsData_t*)malloc(sizeof(sensorsData_t));

    xTaskCreate(sensorsFinished, "Sensor Finished", 2560, jsonData, 10, NULL);

    if (onlineMode) {
        xTaskCreate(sensor_sht31, "Sensor SHT31", 2560, jsonData, 10, NULL);
        xTaskCreate(sensor_as7341, "Sensor AS7341", 2560, jsonData, 10, NULL);
    }

    xTaskCreate(sensor_vl53l0x, "Sensor VL53L0X", 2048, jsonData, 10, NULL);
    xTaskCreate(sensor_soil, "Sensor Soil", 2048, jsonData, 10, NULL);
}