#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_log.h"
#include <string.h>
#include "safety.h"

#define PERIODIC_INTERVAL 1U            // run safety checks every 1sec
#define SENSOR_GRACE_PERIOD (60U * 30U) // period that a sensor can report the same reading in seconds

static const char *TAG = "smart-oil-heater-control-system-safety";
static SemaphoreHandle_t xMutexAccessSafety = NULL;
static sSensorSanityCheck sanityChecks[NUMBER_OF_SENSOR_SANITY_CHECKS] = {
    {SENSOR_NO_ERROR, "chamber_temperature", {95.0f, -10.0f}, 0.0f, 0U, getChamberTemperature},
    {SENSOR_NO_ERROR, "outdoor_temperature", {45.0f, -20.0f}, 0.0f, 0U, getOutdoorTemperature},
    {SENSOR_NO_ERROR, "inlet_flow_temperature", {95.0f, -10.0f}, 0.0f, 0U, getInletFlowTemperature},
    {SENSOR_NO_ERROR, "return_flow_temperature", {95.0f, -10.0f}, 0.0f, 0U, getReturnFlowTemperature}};
static eSafetyState sSafetyState = SAFETY_NO_ERROR;

void taskSafety(void *pvParameters);
void checkSensorSanity(void);
void setSafeState(void);

void initSafety(void)
{
    xMutexAccessSafety = xSemaphoreCreateRecursiveMutex();
    if (xMutexAccessSafety == NULL)
    {
        ESP_LOGE(TAG, "Unable to create mutex");
    }
    xSemaphoreGiveRecursive(xMutexAccessSafety);

    BaseType_t taskCreated = xTaskCreate(
        taskSafety,   // Function to implement the task
        "taskSafety", // Task name
        4096,         // Stack size (in words, not bytes)
        NULL,         // Parameters to the task function (none in this case)
        5,            // Task priority (higher number = higher priority)
        NULL          // Task handle (optional)
    );

    if (taskCreated == pdPASS)
    {
        ESP_LOGI(TAG, "Task created successfully!");
    }
    else
    {
        ESP_LOGE(TAG, "Failed to create task");
    }

    setSafeState(); // Set inital state
}

void taskSafety(void *pvParameters)
{
    while (1)
    {
        vTaskDelay(PERIODIC_INTERVAL * 1000U / portTICK_PERIOD_MS);

        if (xSemaphoreTakeRecursive(xMutexAccessSafety, portMAX_DELAY) == pdTRUE)
        {

            checkSensorSanity();

            if (sSafetyState != SAFETY_NO_ERROR)
            {
                setSafeState();
            }

            xSemaphoreGiveRecursive(xMutexAccessSafety);
        }
    }
}

void checkSensorSanity(void)
{
    sSafetyState = SAFETY_NO_ERROR;
    for (int i = 0; i < NUMBER_OF_SENSOR_SANITY_CHECKS; i++)
    {
        // printf("Check sanity of sensor %s:\n", sanityChecks[i].name);
        // printf("  state: %u\n", sanityChecks[i].state);
        // printf("  Sensor Limits: Max = %.2f, Min = %.2f\n", sanityChecks[i].sSensorLimit.max, sanityChecks[i].sSensorLimit.min);
        // printf("  Last Sensor Temperature: %.2f\n", sanityChecks[i].fSensorTemperatureLast);

        const sMeasurement sCurrentMeasurement = sanityChecks[i].getSensor();

        if (sCurrentMeasurement.state == MEASUREMENT_FAULT)
        {
            ESP_LOGE(TAG, "%s Sensor not found!", sanityChecks[i].name);
            sanityChecks[i].state = SENSOR_NOT_FOUND;
            sSafetyState = SAFETY_SENSOR_ERROR;
        }
        else
        {
            if (sCurrentMeasurement.fCurrentValue == sanityChecks[i].fSensorTemperatureLast)
            {
                sanityChecks[i].uUnchangedCounter++;
                if (sanityChecks[i].uUnchangedCounter >= (SENSOR_GRACE_PERIOD / PERIODIC_INTERVAL))
                {
                    ESP_LOGE(TAG, "%s Sensor reported unchanged value! %lf == %lf", sanityChecks[i].name, sCurrentMeasurement.fCurrentValue, sanityChecks[i].fSensorTemperatureLast);
                    sanityChecks[i].state = SENSOR_UNCHANGED;
                    sSafetyState = SAFETY_SENSOR_ERROR;
                }
            }
            else
            {
                sanityChecks[i].fSensorTemperatureLast = sCurrentMeasurement.fCurrentValue;

                if (sCurrentMeasurement.fCurrentValue > sanityChecks[i].sSensorLimit.max)
                {
                    ESP_LOGE(TAG, "%s Sensor reported too high value! %lf > %lf", sanityChecks[i].name, sCurrentMeasurement.fCurrentValue, sanityChecks[i].sSensorLimit.max);
                    sanityChecks[i].state = SENSOR_TOO_HIGH;
                    sSafetyState = SAFETY_SENSOR_ERROR;
                }
                else if (sCurrentMeasurement.fCurrentValue < sanityChecks[i].sSensorLimit.min)
                {
                    ESP_LOGE(TAG, "%s Sensor reported too low value! %lf < %lf", sanityChecks[i].name, sCurrentMeasurement.fCurrentValue, sanityChecks[i].sSensorLimit.min);
                    sanityChecks[i].state = SENSOR_TOO_LOW;
                    sSafetyState = SAFETY_SENSOR_ERROR;
                }
                else
                {
                    sanityChecks[i].uUnchangedCounter = 0U;
                    sanityChecks[i].state = SENSOR_NO_ERROR;
                }
            }
        }
        // printf("  state: %u\n", sanityChecks[i].state);
    }
}

void setSafeState(void)
{
    setCirculationPumpState(ENABLED); // To cool down system
    setBurnerState(DISABLED);         // Deactivate burner
    setSafetyControlState(DISABLED);  // Disable power to Burner
}

void getSensorSanityStates(sSensorSanityCheck *pSensorSanityChecks)
{
    if (xSemaphoreTakeRecursive(xMutexAccessSafety, pdMS_TO_TICKS(5000)) == pdTRUE)
    {
        for (size_t i = 0; i < NUMBER_OF_SENSOR_SANITY_CHECKS; i++)
        {
            // Copy only the needed attributes
            pSensorSanityChecks[i].state = sanityChecks[i].state;
            strcpy(pSensorSanityChecks[i].name, sanityChecks[i].name);
        }
        xSemaphoreGiveRecursive(xMutexAccessSafety);
    }
    else
    {
        ESP_LOGE(TAG, "Unable to take mutex: getSensorSanityStates()");
    }
}

eSafetyState getSafetyState(void)
{
    eSafetyState state = SAFETY_NO_ERROR;
    if (xSemaphoreTakeRecursive(xMutexAccessSafety, pdMS_TO_TICKS(5000)) == pdTRUE)
    {
        state = sSafetyState;
        xSemaphoreGiveRecursive(xMutexAccessSafety);
    }
    else
    {
        state = SAFETY_INTERNAL_ERROR;
        ESP_LOGE(TAG, "Unable to take mutex: getSafetyState()");
    }
    return state;
}