#include "measured.h"

#include <string>
#include <vector>

#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_log.h"

#include "qthing.h"
#include "mqtt_common.h"


typedef struct {
    std::string name;
    std::string type;
    std::string unit;
    qthing::measured::sensor_t sensor;
} sensor_config_t;

static void esp_log_backend(const std::vector<qthing::measured::sensor_data_t>& data) {
    for (auto const& d: data) {
        ESP_LOGI("measured", "name=%s type=%s unit=%s value=%f", d.name->c_str(), d.type->c_str(), d.unit->c_str(), d.value);
    }
}

static uint16_t next_daemon = 0;

static void measured_wrapper(void *parameter) {
    std::function<void()> *f = (std::function<void()> *)parameter;
    (*f)();
}


/*
 *  Measured implementation
 */

qthing::measured::Measured::Measured() {
    this->backend = esp_log_backend;
    this->register_backend(qthing::measured::backend::mqtt);  // TODO: move somewhere else!

    char task_name[16];
    sprintf(task_name, "measured[%d]", next_daemon++);  // TODO: increment thread-safe!

    std::function<void()> f = std::bind(&qthing::measured::Measured::Measured::measured, this);
    std::function<void()> *_f = new std::function<void()>(f);
    xTaskCreate(measured_wrapper, task_name, 8192, (void*)_f, 1, NULL);
}

void qthing::measured::Measured::measured() {
    TickType_t lastWakeTime = xTaskGetTickCount();

    while(true) {
        std::vector<qthing::measured::sensor_data_t> data;

        for (auto const& sensor: sensors) {
            qthing::measured::sensor_data_t tmp = {
                .name = &sensor.name,
                .type = &sensor.type,
                .unit = &sensor.unit,
                .value = sensor.sensor()
            };

            data.push_back(tmp);
        }

        this->backend(data);

        vTaskDelayUntil(&lastWakeTime, this->measure_period / portTICK_PERIOD_MS);
    }
}

void qthing::measured::Measured::register_sensor(std::string name, std::string type, std::string unit, qthing::measured::sensor_t sensor) {
    sensor_config_t cfg = {
        .name = name,
        .type = type,
        .unit = unit,
        .sensor = sensor
    };

    this->sensors.push_back(cfg);
}


void qthing::measured::Measured::register_backend(qthing::measured::backend_t new_backend) {
    qthing::measured::backend_t old_backend = this->backend;
    
    this->backend = [old_backend, new_backend](const std::vector<qthing::measured::sensor_data_t>& data) {
        old_backend(data);
        new_backend(data);
    };
}

void qthing::measured::Measured::set_measure_period(uint16_t period) {
    this->measure_period = period;
}


/*
 *  Global default instance
 */

static qthing::measured::Measured *md = NULL;

static void start_measured() {
    if (md == NULL) {  // TODO: thread-safe!
        md = new qthing::measured::Measured();
    }
}

void qthing::measured::register_sensor(std::string name, std::string type, std::string unit, qthing::measured::sensor_t sensor) {
    start_measured();
    md->register_sensor(name, type, unit, sensor);
}

void qthing::measured::register_backend(qthing::measured::backend_t new_backend) {
    start_measured();
    md->register_backend(new_backend);
}

void qthing::measured::set_measure_period(uint16_t period) {
    start_measured();
    md->set_measure_period(period);
}


/*
 *  Predefined backends
 */

void qthing::measured::backend::mqtt(const std::vector<qthing::measured::sensor_data_t>& data) {
    if (is_mqtt_connected()) {
        for (auto const& d: data) {
            char message[16];
            sprintf(message, "%0.2f", d.value);
            std::string topic = std::string(DEVICE_NAMESPACE) + std::string(d.name->c_str()) + std::string("/") + std::string(d.type->c_str());

            publish_message(topic, message);
        }
    }
}