#include "datasave.h"
#include <math.h>

static const size_t min_free = 1024 * 1024; // minimum free space in LittleFS to allow saving history (1MB)

LITTLEFSGuard::LITTLEFSGuard()
{
    if (!LittleFS.begin(true, "/littlefs", 10, "littlefs"))
    {
        LOG_ERROR("Failed to mount LittleFS");
    }
    else
    {
        LOG_INFO("LittleFS mounted successfully");
        LOG_INFO("LittleFS Free KBytes:", (LittleFS.totalBytes() - LittleFS.usedBytes()) /1024);
    }
}

LITTLEFSGuard::~LITTLEFSGuard()
{
    LittleFS.end();
    LOG_INFO("LittleFS unmounted successfully");
}

void ignitionBoxStatusAverage::filter(int32_t &old, const int32_t value, const uint32_t k)
{
    float alpha = 1.0f / (float)k;
    old = old + (int32_t)(alpha * (float)(value - old));
}

void ignitionBoxStatusAverage::filter(float &old, const float value, const uint32_t k)
{
    float alpha = 1.0f / (float)k;
    old = old + (float)(alpha * (float)(value - old));
}

void ignitionBoxStatusAverage::reset()
{
    m_last = ignitionBoxStatus();
    m_count = 0;
    m_data_valid = false;
}

void ignitionBoxStatusAverage::update(const ignitionBoxStatus &new_status)
{
    if (m_count == 0 && !m_data_valid)
    {
        m_last = new_status;
    }
    m_count++;
    // simple moving average calculation
    m_last.timestamp = new_status.timestamp; // keep timestamp of latest status

    m_last.coils12.n_events = new_status.coils12.n_events;                           // sum events instead of averaging
    m_last.coils12.n_missed_firing = new_status.coils12.n_missed_firing;             // sum missed firings instead of averaging
    m_last.coils12.spark_status = new_status.coils12.spark_status;                   // take latest spark status
    m_last.coils12.sstart_status = new_status.coils12.sstart_status;                 // take latest soft start status
    filter(m_last.coils12.spark_delay, new_status.coils12.spark_delay, m_max_count); // incremental average calculation
    filter(m_last.coils12.peak_p_in, new_status.coils12.peak_p_in, m_max_count);     // incremental average calculation
    filter(m_last.coils12.peak_n_in, new_status.coils12.peak_n_in, m_max_count);     // incremental average calculation
    filter(m_last.coils12.peak_p_out, new_status.coils12.peak_p_out, m_max_count);   // incremental average calculation
    filter(m_last.coils12.peak_n_out, new_status.coils12.peak_n_out, m_max_count);   // incremental average calculation

    m_last.coils34.n_events = new_status.coils34.n_events;                           // sum events instead of averaging
    m_last.coils34.n_missed_firing = new_status.coils34.n_missed_firing;             // sum missed firings instead of averaging
    m_last.coils34.spark_status = new_status.coils34.spark_status;                   // take latest spark status
    m_last.coils34.sstart_status = new_status.coils34.sstart_status;                 // take latest soft start status
    filter(m_last.coils34.spark_delay, new_status.coils34.spark_delay, m_max_count); // incremental average calculation
    filter(m_last.coils34.peak_p_in, new_status.coils34.peak_p_in, m_max_count);     // incremental average calculation
    filter(m_last.coils34.peak_n_in, new_status.coils34.peak_n_in, m_max_count);     // incremental average calculation
    filter(m_last.coils34.peak_p_out, new_status.coils34.peak_p_out, m_max_count);   // incremental average calculation
    filter(m_last.coils34.peak_n_out, new_status.coils34.peak_n_out, m_max_count);   // incremental average calculation
    filter(m_last.eng_rpm, new_status.eng_rpm, m_max_count);                         // incremental average calculation                   // incremental average calculation
    filter(m_last.adc_read_time, m_last.adc_read_time, m_max_count);                 // incremental average calculation
    m_last.n_queue_errors = new_status.n_queue_errors;                               // take last of queue errors since it's a cumulative count of errors in the queue, not an average value

    if (m_count >= m_max_count)
    {
        m_count = 0;         // reset count after reaching max samples to average
        m_data_valid = true; // set data valid flag after first average is calculated
    }
}

const bool ignitionBoxStatusAverage::get(ignitionBoxStatus &status) const
{
    if (m_data_valid)
    {
        status = m_last;
    }
    return m_data_valid;
}

const ArduinoJson::JsonDocument ignitionBoxStatusAverage::toJson() const
{
    ArduinoJson::JsonDocument doc;
    if (m_data_valid)
    {
        doc["timestamp"] = m_last.timestamp;
        doc["datavalid"] = m_data_valid ? "TRUE" : "FALSE";

        doc["coils12"]["n_events"] = m_last.coils12.n_events;
        doc["coils12"]["n_missed_firing"] = m_last.coils12.n_missed_firing;
        doc["coils12"]["spark_delay"] = m_last.coils12.spark_delay;
        doc["coils12"]["spark_status"] = sparkStatusNames.at(m_last.coils12.spark_status);
        doc["coils12"]["peak_p_in"] = m_last.coils12.peak_p_in;
        doc["coils12"]["peak_n_in"] = m_last.coils12.peak_n_in;
        doc["coils12"]["peak_p_out"] = m_last.coils12.peak_p_out;
        doc["coils12"]["peak_n_out"] = m_last.coils12.peak_n_out;
        doc["coils12"]["sstart_status"] = softStartStatusNames.at(m_last.coils12.sstart_status);

        doc["coils34"]["n_events"] = m_last.coils34.n_events;
        doc["coils34"]["n_missed_firing"] = m_last.coils34.n_missed_firing;
        doc["coils34"]["spark_delay"] = m_last.coils34.spark_delay;
        doc["coils34"]["spark_status"] = sparkStatusNames.at(m_last.coils34.spark_status);
        doc["coils34"]["peak_p_in"] = m_last.coils34.peak_p_in;
        doc["coils34"]["peak_n_in"] = m_last.coils34.peak_n_in;
        doc["coils34"]["peak_p_out"] = m_last.coils34.peak_p_out;
        doc["coils34"]["peak_n_out"] = m_last.coils34.peak_n_out;
        doc["coils34"]["sstart_status"] = softStartStatusNames.at(m_last.coils34.sstart_status);

        doc["eng_rpm"] = m_last.eng_rpm;
        doc["adc_read_time"] = m_last.adc_read_time;
        doc["n_queue_errors"] = m_last.n_queue_errors;
    }
    return doc;
}

void saveHistoryTask(void *pvParameters)
{
    const auto *params = static_cast<dataSaveParams *>(pvParameters);
    const auto &history = *params->history;
    const auto &file_path = params->file_path;
    if (!params)
    {
        LOG_ERROR("Invalid parameters for saveHistoryTask");
        return;
    }
    LOG_DEBUG("Starting saving: ", file_path.c_str());
    save_history(history, file_path);
    vTaskDelete(NULL);
}

void save_history(const PSRAMVector<ignitionBoxStatus> &history, const std::filesystem::path &file_name)
{
    // Initialize SPIFFS
    if (!SAVE_HISTORY_TO_LITTLEFS)
        return;

    auto littlefs_guard = LITTLEFSGuard(); // use RAII guard to ensure LittleFS is properly mounted and unmounted

    if (LittleFS.totalBytes() - LittleFS.usedBytes() < min_free) // check if at least 1MB is free for saving history
    {
        LOG_ERROR("Not enough space in SPIFFS to save history");
        return;
    }

    std::filesystem::path file_path = file_name;
    if (file_name.root_path() != "/littlefs")
        file_path = std::filesystem::path("/littlefs") / file_name;

    auto save_flags = std::ios::out;
    if (first_save && LittleFS.exists(file_path.c_str()))
    {
        first_save = false;
        save_flags |= std::ios::trunc;      // overwrite existing file
        LittleFS.remove(file_path.c_str()); // ensure file is removed before saving to avoid issues with appending to existing file in SPIFFS
        LOG_INFO("Saving history to LittleFS, new file:", file_path.c_str());
    }
    else
    {
        save_flags |= std::ios::app; // append to new file
        LOG_INFO("Saving history to LittleFS, appending to existing file:", file_path.c_str());
    }

    std::ofstream ofs(file_path, save_flags);
    if (ofs.fail())
    {
        LOG_ERROR("Failed to open file for writing");
        return;
    }

    // write csv header
    if (first_save)
    {
        ofs << "TS,\
        EVENTS_12,DLY_12,STAT_12,V_12_1,V_12_2,V_12_3,V_12_4,IGNITION_MODE_12,\
        EVENTS_34,DLY_34,STAT_34,V_34_1,V_34_2,V_34_3,V_34_4,IGNITION_MODE_34,\
        ENGINE_RPM,ADC_READTIME,N_QUEUE_ERRORS"
            << std::endl;
        ofs.flush();
    }

    for (const auto &entry : history)
    {
        ofs << std::to_string(entry.timestamp) << ","
            << std::to_string(entry.coils12.n_events) << ","
            << std::to_string(entry.coils12.spark_delay) << ","
            << std::string(sparkStatusNames.at(entry.coils12.spark_status)) << ","
            << std::to_string(entry.coils12.peak_p_in) << ","
            << std::to_string(entry.coils12.peak_n_in) << ","
            << std::to_string(entry.coils12.peak_p_out) << ","
            << std::to_string(entry.coils12.peak_n_out) << ","
            << std::string(softStartStatusNames.at(entry.coils12.sstart_status)) << ","
            << std::to_string(entry.coils34.n_events) << ","
            << std::to_string(entry.coils34.spark_delay) << ","
            << std::string(sparkStatusNames.at(entry.coils34.spark_status)) << ","
            << std::to_string(entry.coils34.peak_p_in) << ","
            << std::to_string(entry.coils34.peak_n_in) << ","
            << std::to_string(entry.coils34.peak_p_out) << ","
            << std::to_string(entry.coils34.peak_n_out) << ","
            << std::string(softStartStatusNames.at(entry.coils34.sstart_status)) << ","
            << std::to_string(entry.eng_rpm) << ","
            << std::to_string(entry.adc_read_time) << ","
            << std::to_string(entry.n_queue_errors);
        ofs << std::endl;
        ofs.flush();
    }

    ofs.close();
    LOG_INFO("Ignition A history saved to LittleFS, records written: ", history.size());
}
