/* SleepLib Resvent Loader Implementation
 *
 * Copyright (c) 2019-2023 The OSCAR Team
 * Copyright (c) 2011-2018 Mark Watkins <mark@jedimark.net>
 *
 * This file is subject to the terms and conditions of the GNU General Public
 * License. See the file COPYING in the main directory of the source code
 * for more details. */

//********************************************************************************************
// Please only INCREMENT the resvent_data_version in resvent_loader.h when making changes
// that change loader behaviour or modify channels in a manner that fixes old data imports.
// Note that changing the data version will require a reimport of existing data for which OSCAR
// does not keep a backup - so it should be avoided if possible.
// i.e. there is no need to change the version when adding support for new devices
//********************************************************************************************

// Turn off for offical release.
#define TEST_MACROS_ENABLEDoff
#include <test_macros.h>

#include <QCoreApplication>
#include <QString>
#include <QDateTime>
#include <QDir>
#include <QDirIterator>
#include <QFile>
#include <QDebug>
#include <QVector>
#include <QMap>
#include <QStringList>
#include <cmath>

#include "resvent_loader.h"

#ifdef DEBUG_EFFICIENCY
#include <QElapsedTimer>  // only available in 4.8
#endif

// Files WXX_XX contain flow rate and pressure and PXX_XX contain Pressure, IPAP, EPAP, Leak, Vt, MV, RR, Ti, IE, Spo2, PR
// Both files contain a little header of size 0x24 bytes. In offset 0x12 contain the total number of different records in
// the different files of the same type. And later contain the previous describe quantity of description header of size 0x20
// containing the details for every type of record (e.g. sample chunk size).

ResventLoader::ResventLoader()
{
    const QString RESVENT_ICON = ":/icons/resvent.png";

    QString s = newInfo().series;
    m_pixmap_paths[s] = RESVENT_ICON;
    m_pixmaps[s] = QPixmap(RESVENT_ICON);

    m_type = MT_CPAP;
}
ResventLoader::~ResventLoader()
{
}

const QString kResventTherapyFolder = "THERAPY";
const QString kResventConfigFolder = "CONFIG";
const QString kResventRecordFolder = "RECORD";
const QString kResventSysConfigFilename = "SYSCFG";
constexpr qint64 kDateTimeOffset = 7 * 60 * 60 * 1000;    // why is 7 hours added.??? COnvert to gmt??
constexpr int kMainHeaderSize = 0x24;
constexpr int kDescriptionHeaderSize = 0x20;
constexpr int kChunkDurationInSecOffset = 0x10;
constexpr int kDescriptionCountOffset = 0x12;
constexpr int kDescriptionSamplesByChunk = 0x1e;
constexpr double kMilliGain = 0.001;
constexpr double kHundredthGain = 0.01;
constexpr double kTenthGain = 0.1;
constexpr double kNoGain = 1.0;

constexpr double kDefaultGain = kHundredthGain ;           // For Flow (rate) and (mask)Pressure - High Resolutions data.

bool ResventLoader::Detect(const QString & givenpath)
{
    QDir dir(givenpath);

    if (!dir.exists()) {
        return false;
    }

    if (!dir.exists(kResventTherapyFolder)) {
        return false;
    }

    dir.cd(kResventTherapyFolder);
    if (!dir.exists(kResventConfigFolder)) {
        return false;
    }

    if (!dir.cd(kResventRecordFolder)) {
        return false;
    }
    return true;
}


MachineInfo ResventLoader::PeekInfo(const QString & path)
{
    if (!Detect(path)) {
        return MachineInfo();
    }

    MachineInfo info = newInfo();

    const auto sys_config_path = path + QDir::separator() + kResventTherapyFolder + QDir::separator() + kResventConfigFolder + QDir::separator() + kResventSysConfigFilename;
    if (!QFile::exists(sys_config_path)) {
        qDebug() << "Resvent Data card has no" << kResventSysConfigFilename << "file in " << sys_config_path;
        return MachineInfo();
    }
    QFile f(sys_config_path);
    f.open(QIODevice::ReadOnly | QIODevice::Text);
    f.seek(4);
    while (!f.atEnd()) {
        QString line = f.readLine().trimmed();

        const auto elems = line.split("=");
        Q_ASSERT(elems.size() == 2);

        if (elems[0] == "models") {
            info.model = elems[1];
        }
        else if (elems[0] == "sn") {
            info.serial = elems[1];
        }
        else if (elems[0] == "num") {
            info.version = elems[1].toInt();
        }
        else if (elems[0] == "num") {
            info.type = MachineType::MT_CPAP;
        }
    }

    if (info.model.contains("Point", Qt::CaseInsensitive)) {
        info.brand = "Hoffrichter";
    } else {
        info.brand = "Resvent";
    }

    return info;
}

QVector<QDate> GetSessionsDate(const QString& dirpath) {
    QVector<QDate> sessions_date;

    const auto records_path = dirpath + QDir::separator() + kResventTherapyFolder + QDir::separator() + kResventRecordFolder;
    QDir records_folder(records_path);
    const auto year_month_folders = records_folder.entryList(QStringList(), QDir::Dirs|QDir::NoDotAndDotDot, QDir::Name);
    std::for_each(year_month_folders.cbegin(), year_month_folders.cend(), [&](const QString& year_month_folder_name){
        if (year_month_folder_name.length() != 6) {
            return;
        }

        const int year = std::stoi(year_month_folder_name.left(4).toStdString());
        const int month = std::stoi(year_month_folder_name.right(2).toStdString());

        const auto year_month_folder_path = records_path + QDir::separator() + year_month_folder_name;
        QDir year_month_folder(year_month_folder_path);
        const auto session_folders = year_month_folder.entryList(QStringList(), QDir::Dirs|QDir::NoDotAndDotDot, QDir::Name);
        std::for_each(session_folders.cbegin(), session_folders.cend(), [&](const QString& day_folder){
            const auto day = std::stoi(day_folder.toStdString());

            sessions_date.push_back(QDate(year, month, day));
        });
    });
    return sessions_date;
}

enum class EventType {
    //UsageSec = 1,
    //UnixStart = 2,
    ObstructiveApnea = 17,
    CentralApnea = 18,
    Hypopnea = 19,
    FlowLimitation = 20,
    RERA = 21,
    PeriodicBreathing = 22,
    Snore = 23
};

struct EventData {
    EventType type;
    QDateTime date_time;
    int duration;
};

struct UsageData {
    QString number{};
    QDateTime start_time{};
    QDateTime end_time{};
    qint32 countAHI = 0;
    qint32 countOAI = 0;
    qint32 countCAI = 0;
    qint32 countAI = 0;
    qint32 countHI = 0;
    qint32 countRERA = 0;
    qint32 countSNI = 0;
    qint32 countBreath = 0;
};

void UpdateEvents(EventType event_type, const QMap<EventType, QVector<EventData>>& events, Session* session) {
    static QMap<EventType, unsigned int> mapping {{EventType::ObstructiveApnea, CPAP_Obstructive},
                                                 {EventType::CentralApnea, CPAP_ClearAirway},
                                                 {EventType::Hypopnea, CPAP_Hypopnea},
                                                 {EventType::FlowLimitation, CPAP_FlowLimit},
                                                 {EventType::RERA, CPAP_RERA},
                                                 {EventType::PeriodicBreathing, CPAP_PB},
                                                 {EventType::Snore, CPAP_Snore}};
    const auto it_events = events.find(event_type);
    const auto it_mapping = mapping.find(event_type);
    if (it_events == events.cend() || it_mapping == mapping.cend()) {
        return;
    }

    EventList* event_list  = session->AddEventList(it_mapping.value(), EVL_Event);
    std::for_each(it_events.value().cbegin(), it_events.value().cend(), [&](const EventData& event_data){
        event_list->AddEvent(event_data.date_time.toMSecsSinceEpoch() + kDateTimeOffset, event_data.duration);
    });
}

QString GetSessionFolder(const QString& dirpath, const QDate& session_date) {
    const auto year_month_folder = QString("%1%2").arg(session_date.year()).arg(session_date.month(),2,10,QLatin1Char('0'));
    const auto day_folder = QString("%1").arg(session_date.day(),2,10,QLatin1Char('0')) ;
    const auto session_folder_path = dirpath + QDir::separator() + kResventTherapyFolder + QDir::separator() + kResventRecordFolder + QDir::separator() + year_month_folder + QDir::separator() + day_folder;
    return session_folder_path;
}

bool VerifyEvent(EventData& eventData) {
    switch (eventData.type) {
        case EventType::FlowLimitation:
        case EventType::Hypopnea:
        case EventType::ObstructiveApnea:       // OA
        case EventType::CentralApnea:           // CA and same clear airway.
            // adjust time of event to be after the event ends rather than when the event starts.
            eventData.date_time = eventData.date_time.addMSecs(eventData.duration*1000);
            break;
        case EventType::RERA:
            eventData.duration = 0 ;    // duration is large and suppress duration display of eariler OA events.
            break;
        case EventType::PeriodicBreathing:
        case EventType::Snore:
        default:
            break;
    }
    return true;
}

void LoadEvents(const QString& session_folder_path, Session* session, const UsageData& usage ) {
    const auto event_file_path = session_folder_path + QDir::separator() + "EV" + usage.number;
    // Oscar (resmed) plots events at end.

    QMap<EventType, QVector<EventData>> events;
    QFile f(event_file_path);
    f.open(QIODevice::ReadOnly | QIODevice::Text);
    f.seek(4);
    while (!f.atEnd()) {
        QString line = f.readLine().trimmed(); // ID=20,DT=1692022874,DR=6,GD=0,

#if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)
        const auto elems = line.split(",", Qt::SkipEmptyParts);
#else
        const auto elems = line.split(",", QString::SkipEmptyParts);
#endif
        if (elems.size() != 4) {
            continue;
        }

        const auto event_type_elems = elems.at(0).split("=");
        const auto date_time_elems = elems.at(1).split("=");
        const auto duration_elems = elems.at(2).split("=");

        Q_ASSERT(event_type_elems.size() == 2);
        Q_ASSERT(date_time_elems.size() == 2);
        Q_ASSERT(duration_elems.size() == 2);
        auto event_type = static_cast<EventType>(std::stoi(event_type_elems[1].toStdString()));
        auto duration = std::stoi(duration_elems[1].toStdString());
        auto date_time = QDateTime::fromTime_t(std::stoi(date_time_elems[1].toStdString()));

        EventData eventData({event_type, date_time, duration});
        VerifyEvent(eventData);
        events[event_type].push_back(eventData);
    }

    static QVector<EventType> mapping {EventType::ObstructiveApnea,
                                      EventType::CentralApnea,
                                      EventType::Hypopnea,
                                      EventType::FlowLimitation,
                                      EventType::RERA,
                                      EventType::PeriodicBreathing,
                                      EventType::Snore};

    std::for_each(mapping.cbegin(), mapping.cend(), [&](EventType event_type){
        UpdateEvents(event_type, events, session);
    });
}

template <typename T>
T read_from_file(QFile& f) {
    T data{};
    f.read(reinterpret_cast<char*>(&data), sizeof(T));
    return data;
}

struct WaveFileData {
    unsigned int wave_event_id;
    QString file_base_name;
    unsigned int sample_rate_offset;
    unsigned int start_offset;
};

EventList* GetEventList(const QString& name, Session* session, float sample_rate = 0.0) {
    DEBUGFC Q(name);
    if (name == "Press") {
        return session->AddEventList(CPAP_Pressure, EVL_Event, kHundredthGain);
    }
    else if (name == "IPAP") {
        return session->AddEventList(CPAP_IPAP, EVL_Event, kHundredthGain);
    }
    else if (name == "EPAP") {
        return session->AddEventList(CPAP_EPAP, EVL_Event, kHundredthGain);
    }
    else if (name == "Leak") {
        return session->AddEventList(CPAP_Leak, EVL_Event , kTenthGain);
    }
    else if (name == "Vt") {
        return session->AddEventList(CPAP_TidalVolume, EVL_Event , kNoGain);
    }
    else if (name == "MV") {
        return session->AddEventList(CPAP_MinuteVent, EVL_Event , kHundredthGain);
    }
    else if (name == "RR") {
        return session->AddEventList(CPAP_RespRate, EVL_Event, kTenthGain);
    }
    else if (name == "Ti") {
        return session->AddEventList(CPAP_Ti, EVL_Event, kMilliGain);
    }
    else if (name == "I:E") {
        return session->AddEventList(CPAP_IE, EVL_Event, kMilliGain);
    } else if (name == "SpO2" || name == "PR") {
        // Not present
        return nullptr;
    } else if (name == "Pressure") {
        return session->AddEventList(CPAP_MaskPressure, EVL_Waveform, kDefaultGain, 0.0, 0.0, 0.0, 1000.0 / sample_rate);
    }
    else if (name == "Flow") {
        return session->AddEventList(CPAP_FlowRate, EVL_Waveform, kDefaultGain, 0.0, 0.0, 0.0, 1000.0 / sample_rate);
    }
    else {
        // Not supported
        Q_ASSERT(false);
        return nullptr;
    }
}

struct ChunkData {
    EventList* event_list;
    uint16_t samples_by_chunk;
    qint64 start_time;
    int total_samples_by_chunk;
    float sample_rate;
    #ifdef TEST_MACROS_ENABLED
    QString chunkName ;
    int chunkDebug = -1;
    #endif
};

QString ReadDescriptionName(QFile& f) {
    constexpr int kNameSize = 9;
    QVector<char> name(kNameSize);

    const auto readed = f.read(name.data(), kNameSize - 1);
    Q_ASSERT(readed == kNameSize - 1);

    return QString(name.data());
}

void ReadWaveFormsHeaders(QFile& f, QVector<ChunkData>& wave_forms, Session* session, const UsageData& usage) {
    f.seek(kChunkDurationInSecOffset);
    const auto chunk_duration_in_sec = read_from_file<uint16_t>(f);
    f.seek(kDescriptionCountOffset);
    const auto description_count = read_from_file<uint16_t>(f);
    wave_forms.resize(description_count);
	DEBUGFC Q(chunk_duration_in_sec) Q(description_count);

    for (unsigned int i = 0; i < description_count; i++) {
        const auto description_header_offset = kMainHeaderSize + i * kDescriptionHeaderSize;
        f.seek(description_header_offset);
        const auto name = ReadDescriptionName(f);
        f.seek(description_header_offset + kDescriptionSamplesByChunk);
        const auto samples_by_chunk = read_from_file<uint16_t>(f);

        wave_forms[i].sample_rate = 1.0 * samples_by_chunk / chunk_duration_in_sec;
        wave_forms[i].event_list = GetEventList(name, session, wave_forms[i].sample_rate);
        wave_forms[i].samples_by_chunk = samples_by_chunk;
        wave_forms[i].start_time = usage.start_time.toMSecsSinceEpoch();
        #ifdef TEST_MACROS_ENABLED
        wave_forms[i].chunkName = name;
        wave_forms[i].chunkDebug = -1;

        DEBUGNC O( wave_forms[i].chunkName) DATETIME(wave_forms[i].start_time) O(usage.number);
        //IF ((name == "I:E" || name == "Ti") && f.fileName().contains("P01_01"))
        //DEBUGFC Q( wave_forms[i].chunkName) O(f.fileName()) ;
        IF ( (name == "Leak") &&  f.fileName().contains("P01_01"))
        OO ({
            wave_forms[i].chunkDebug = 1;
            DEBUGFC Q( wave_forms[i].chunkName) DATETIME(wave_forms[i].start_time) O("\n") O(f.fileName()) ;
        })
        #endif
    }
}

void LoadOtherWaveForms(const QString& session_folder_path, Session* session, const UsageData& usage) {
    QDir session_folder(session_folder_path);

    const auto wave_files = session_folder.entryList(QStringList() << "P" + usage.number + "_*", QDir::Files, QDir::Name);

    QVector<ChunkData> wave_forms;
    bool initialized = false;
    std::for_each(wave_files.cbegin(), wave_files.cend(), [&](const QString& wave_file){
        // P01_ file
        DEBUGFC O("LoadOtherWaveForms") Q(usage.number);
        QFile f(session_folder_path + QDir::separator() + wave_file);
        f.open(QIODevice::ReadOnly);

        if (!initialized) {
            ReadWaveFormsHeaders(f, wave_forms, session, usage);
            initialized = true;
        }
        f.seek(kMainHeaderSize + wave_forms.size() * kDescriptionHeaderSize);

        std::vector<qint16> chunk(std::max_element(wave_forms.cbegin(), wave_forms.cend(), [](const ChunkData& lhs, const ChunkData& rhs){
                                      return lhs.samples_by_chunk < rhs.samples_by_chunk;
                                  })->samples_by_chunk);
        while (!f.atEnd()) {
            for (int i = 0; i < wave_forms.size(); i++) {
                const auto& wave_form = wave_forms[i].event_list;
                IF (wave_forms[i].chunkDebug>0) DEBUGFC O(wave_forms[i].chunkName) O(wave_form);
                const auto samples_by_chunk_actual = wave_forms[i].samples_by_chunk;
                auto& start_time_current = wave_forms[i].start_time;
                auto& total_samples_by_chunk = wave_forms[i].total_samples_by_chunk;
                const auto sample_rate = wave_forms[i].sample_rate;

                const auto duration = samples_by_chunk_actual * 1000.0 / sample_rate;
                const auto readed = f.read(reinterpret_cast<char*>(chunk.data()), chunk.size() * sizeof(qint16));
                if (wave_form) {
                    const auto readed_elements = readed / sizeof(qint16);
                    IF (wave_forms[i].chunkDebug>0) DEBUGFC Q(readed_elements) Q(samples_by_chunk_actual);
                    if (readed_elements != samples_by_chunk_actual) {
                        std::fill(std::begin(chunk) + readed_elements, std::end(chunk), 0);
                    }

                    int offset = 0;
             std::for_each(chunk.cbegin(), chunk.cend(), [&](const qint16& value){
                        IF (wave_forms[i].chunkDebug>0 && value>0) DEBUGFC O(wave_forms[i].chunkName) DATETIME(start_time_current + offset + kDateTimeOffset) O(value) Q(offset) Q(sample_rate);
                        wave_form->AddEvent(start_time_current + offset + kDateTimeOffset, value);
                        offset += 1000.0 / sample_rate;
                    });
                }

                start_time_current += duration;
                total_samples_by_chunk += samples_by_chunk_actual;
            }
        }
    });

    QVector<qint16> chunk;
    for (int i = 0; i < wave_forms.size(); i++) {
        const auto& wave_form = wave_forms[i];
        const auto expected_samples = usage.start_time.msecsTo(usage.end_time) / 1000.0 * wave_form.sample_rate;
        if (wave_form.total_samples_by_chunk < expected_samples) {
            chunk.resize(expected_samples - wave_form.total_samples_by_chunk);
            if (wave_form.event_list) {
                int offset = 0;
                std::for_each(chunk.cbegin(), chunk.cend(), [&](const qint16& value){
                    wave_form.event_list->AddEvent(wave_form.start_time + offset + kDateTimeOffset, value );
                    IF (wave_forms[i].chunkDebug>0 && offset>=0 && value > 0)
                        DEBUGFC O(wave_forms[i].chunkName)
                        DATETIME(wave_form.start_time + offset + kDateTimeOffset)
                        O(value)
                        //Q(sample_rate)
                        ;
                    offset += 1000.0 / wave_form.sample_rate;
                });
            }
        }
    }
}

void LoadWaveForms(const QString& session_folder_path, Session* session, const UsageData& usage) {
    QDir session_folder(session_folder_path);

    const auto wave_files = session_folder.entryList(QStringList() << "W" + usage.number + "_*", QDir::Files, QDir::Name);

    QVector<ChunkData> wave_forms;
    bool initialized = false;
    std::for_each(wave_files.cbegin(), wave_files.cend(), [&](const QString& wave_file){
        // W01_ file
        QFile f(session_folder_path + QDir::separator() + wave_file);
        f.open(QIODevice::ReadOnly);

        if (!initialized) {
            ReadWaveFormsHeaders(f, wave_forms, session, usage);
            initialized = true;
        }
        f.seek(kMainHeaderSize + wave_forms.size() * kDescriptionHeaderSize);

        QVector<qint16> chunk(std::max_element(wave_forms.cbegin(), wave_forms.cend(), [](const ChunkData& lhs, const ChunkData& rhs){
                                  return lhs.samples_by_chunk < rhs.samples_by_chunk;
                              })->samples_by_chunk);
        while (!f.atEnd()) {
            for (int i = 0; i < wave_forms.size(); i++) {
                const auto& wave_form = wave_forms[i].event_list;
                const auto samples_by_chunk_actual = wave_forms[i].samples_by_chunk;
                auto& start_time_current = wave_forms[i].start_time;
                auto& total_samples_by_chunk = wave_forms[i].total_samples_by_chunk;
                const auto sample_rate = wave_forms[i].sample_rate;
                //DEBUGFC O(wave_forms[i].chunkName) DATE(wave_forms[i].start_time) Q(usage.number);

                const auto duration = samples_by_chunk_actual * 1000.0 / sample_rate;
                const auto readed = f.read(reinterpret_cast<char*>(chunk.data()), chunk.size() * sizeof(qint16));
                if (wave_form) {
                    const auto readed_elements = readed / sizeof(qint16);
                    if (readed_elements != samples_by_chunk_actual) {
                        std::fill(std::begin(chunk) + readed_elements, std::end(chunk), 0);
                    }
                    wave_form->AddWaveform(start_time_current + kDateTimeOffset, chunk.data(), samples_by_chunk_actual, duration);
                }
                start_time_current += duration;
                total_samples_by_chunk += samples_by_chunk_actual;
            }
        }
    });

    QVector<qint16> chunk;
    for (int i = 0; i < wave_forms.size(); i++) {
        const auto& wave_form = wave_forms[i];
        const auto expected_samples = usage.start_time.msecsTo(usage.end_time) / 1000.0 * wave_form.sample_rate;
        if (wave_form.total_samples_by_chunk < expected_samples) {
            chunk.resize(expected_samples - wave_form.total_samples_by_chunk);
            if (wave_form.event_list) {
                const auto duration = chunk.size() * 1000.0 / wave_form.sample_rate;
                wave_form.event_list->AddWaveform(wave_form.start_time + kDateTimeOffset, chunk.data(), chunk.size(), duration);
            }
        }
    }
}

void LoadStats(const UsageData& /*usage_data*/, Session* session ) {
    session->settings[CPAP_Mode] = MODE_APAP;
    session->settings[CPAP_PressureMin] = 4.0;    // these value are hard coded for now.
    session->settings[CPAP_PressureMax] = 20.0;   // these value are hard coded for now.
}

UsageData ReadUsage(const QString& session_folder_path, const QString& usage_number) {
    UsageData usage_data;
    usage_data.number = usage_number;

    const auto session_stat_path = session_folder_path + QDir::separator() + "STAT" + usage_number;
    if (!QFile::exists(session_stat_path)) {
        qDebug() << "Resvent Data card has no " << session_stat_path;
        return usage_data;
    }
    QFile f(session_stat_path);
    f.open(QIODevice::ReadOnly | QIODevice::Text);
    f.seek(4);
    while (!f.atEnd()) {
        QString line = f.readLine().trimmed();

        const auto elems = line.split("=");
        Q_ASSERT(elems.size() == 2);

        if (elems[0] == "secStart") {
            usage_data.start_time = QDateTime::fromTime_t(std::stoi(elems[1].toStdString()));
        }
        else if (elems[0] == "secUsed") {
            usage_data.end_time = QDateTime::fromTime_t(usage_data.start_time.toTime_t() + std::stoi(elems[1].toStdString()));
        }
        else if (elems[0] == "cntAHI") {
            usage_data.countAHI = std::stoi(elems[1].toStdString());
        }
        else if (elems[0] == "cntOAI") {
            usage_data.countOAI = std::stoi(elems[1].toStdString());
        }
        else if (elems[0] == "cntCAI") {
            usage_data.countCAI = std::stoi(elems[1].toStdString());
        }
        else if (elems[0] == "cntAI") {
            usage_data.countAI = std::stoi(elems[1].toStdString());
        }
        else if (elems[0] == "cntHI") {
            usage_data.countHI = std::stoi(elems[1].toStdString());
        }
        else if (elems[0] == "cntRERA") {
            usage_data.countRERA = std::stoi(elems[1].toStdString());
        }
        else if (elems[0] == "cntSNI") {
            usage_data.countSNI = std::stoi(elems[1].toStdString());
        }
        else if (elems[0] == "cntBreath") {
            usage_data.countBreath = std::stoi(elems[1].toStdString());
        }
    }

    return usage_data;
}

QVector<UsageData> GetDifferentUsage(const QString& session_folder_path) {
    QDir session_folder(session_folder_path);

    const auto stat_files = session_folder.entryList(QStringList() << "STAT*", QDir::Files, QDir::Name);

    QVector<UsageData> usage_data;
    std::for_each(stat_files.cbegin(), stat_files.cend(), [&](const QString& stat_file){
        if (stat_file.size() != 6) {
            return;
        }

        auto usageData = ReadUsage(session_folder_path, stat_file.right(2));

        usage_data.push_back(usageData);
    });
    return usage_data;
}

int LoadSession(const QString& dirpath, const QDate& session_date, Machine* machine) {
    // Handles one day - all OSCAR sessions for a  day.
    const auto session_folder_path = GetSessionFolder(dirpath, session_date);

    const auto different_usage = GetDifferentUsage(session_folder_path);
    //return std::accumulate(different_usage.cbegin(), different_usage.cend(), 0, [&](int base, const UsageData& usage)
    // std::accumulate(different_usage.cbegin(), different_usage.cend(), 0, [&](int base, const UsageData& usage)
    int base = 0;
    for (auto usage : different_usage)
    {
        if (machine->SessionExists(usage.start_time.toMSecsSinceEpoch() + kDateTimeOffset)) {
            // session alreadt exists
            //return base;
            continue;
        }
        Session* session = new Session(machine, usage.start_time.toMSecsSinceEpoch() + kDateTimeOffset);
        session->SetChanged(true);
        session->really_set_first(usage.start_time.toMSecsSinceEpoch() + kDateTimeOffset);
        session->really_set_last(usage.end_time.toMSecsSinceEpoch() + kDateTimeOffset);
        LoadStats(usage, session);
        LoadWaveForms(session_folder_path, session, usage);
        LoadOtherWaveForms(session_folder_path, session, usage);
        LoadEvents(session_folder_path, session, usage);

        session->UpdateSummaries();
        session->Store(machine->getDataPath());
        machine->AddSession(session);
        ++base;
    };
    return base;
}


///////////////////////////////////////////////////////////////////////////////////////////
// Sorted EDF files that need processing into date records according to ResMed noon split
///////////////////////////////////////////////////////////////////////////////////////////
int ResventLoader::Open(const QString & dirpath)
{
    const auto machine_info = PeekInfo(dirpath);

    // Abort if no serial number
    if (machine_info.serial.isEmpty()) {
        qDebug() << "Resvent Data card has no valid serial number in " << kResventSysConfigFilename;
        return -1;
    }

    const auto sessions_date = GetSessionsDate(dirpath);
    int progress =  0;
    emit setProgressMax(1+sessions_date.size());    // add one to include Save in progress.
    emit setProgressValue(progress);
    QCoreApplication::processEvents();

    Machine *machine = p_profile->CreateMachine(machine_info);


    int new_sessions = 0;
    // do for each day found.
    std::for_each(sessions_date.cbegin(), sessions_date.cend(), [&](const QDate& session_date){
        new_sessions += LoadSession(dirpath, session_date, machine);
        emit setProgressValue(++progress);
        QCoreApplication::processEvents();
    });

    machine->Save();

    emit setProgressValue(++progress);
    QCoreApplication::processEvents();
    return new_sessions;
}

void ResventLoader::initChannels()
{
}

ChannelID ResventLoader::PresReliefMode() { return 0; }
ChannelID ResventLoader::PresReliefLevel() { return 0; }
ChannelID ResventLoader::CPAPModeChannel() { return 0; }

bool resvent_initialized = false;
void ResventLoader::Register()
{
    if (resvent_initialized) { return; }

    qDebug() << "Registering ResventLoader";
    RegisterLoader(new ResventLoader());

    resvent_initialized = true;
}