#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"


static void esp_log_backend(const std::vector<qthing::measured::MeasureResult>& data) {
    for (auto const& d: data) {
        ESP_LOGI("measured", "name=%s type=%s unit=%s value=%f", d.sensor_name.c_str(), d.sensor_type.c_str(), d.sensor_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)();
}

qthing::measured::MeasureResult::MeasureResult(const std::string& name, const std::string& type, const std::string& unit, float value) : value(value), sensor_name(name), sensor_type(type), sensor_unit(unit) {}

qthing::measured::Sensor::Sensor(const std::string& name, const std::string& type, const std::string& unit) : name(name), type(type), unit(unit) {}

qthing::measured::Sensor::skip_measurement_t qthing::measured::Sensor::prepare_next_measurement() {
    return(no_skip);
}

qthing::measured::MeasureResult qthing::measured::Sensor::create_result(float value) {
    return(qthing::measured::MeasureResult(this->name, this->type, this->unit, value));
}

/*
 *  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::MeasureResult> data;

        for (auto& sensor: sensors) {
            data.push_back(sensor->measure());
        }

        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 *sensor) {
    this->sensors.push_back(sensor);
}


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::MeasureResult>& 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 *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::MeasureResult>& 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.sensor_name.c_str()) + std::string("/") + std::string(d.sensor_type.c_str());

            publish_message(topic, message);
        }
    }
}