/* SleepLib Session Implementation
 * This stuff contains the base calculation smarts
 *
 * Copyright (c) 2019-2025 The OSCAR Team
 * Copyright (c) 2011-2018 Mark Watkins
 *
 * 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. */

#define TEST_MACROS_ENABLEDoff
#include <test_macros.h>

#include <cmath>
#include <QDebug>
#include <QMessageBox>
#include <QMetaType>
#include <algorithm>
#include <limits>
#include <QDir>

#include "session.h"
#include "version.h"
#include "speedcheck.h"
#include "SleepLib/machine_common.h"
#include "SleepLib/calcs.h"
#include "SleepLib/profiles.h"

using namespace std;
#define FIX_FOR_SINGLE_EVENT            // fixes ibreeze "No valuesummary for channel" error during import.

// This is the uber important database version for OSCAR's internal storage
// Increment this after stuffing with Session's save & load code.
const quint16 summary_version = 18;
const quint16 events_version = 10;

Session::Session(Machine *m, SessionID session)
{
    s_lonesession = false;

    if (!session) {
        session = m->CreateSessionID();
    }

    s_machine = m;
    s_machtype = m->type();
    s_session = session;
    s_changed = false;
    s_events_loaded = false;
    s_summary_loaded = false;
    _first_session = true;
    s_enabled = true;

    s_first = s_last = 0;
    s_evchecksum_checked = false;

    s_noSettings = s_summaryOnly = false;

    destroyed = false;
}

Session::~Session()
{
    TrashEvents();
    destroyed = true;
}

void Session::TrashEvents()
// Trash this sessions Events and release memory.
{
    QVector<EventList *>::iterator j;
    QVector<EventList *>::iterator j_end;
    QHash<ChannelID, QVector<EventList *> >::iterator i;
    QHash<ChannelID, QVector<EventList *> >::iterator i_end=eventlist.end();

    if (s_changed) {
        // Save first..
    }

    for (i = eventlist.begin(); i != i_end; ++i) {
        j_end=i.value().end();
        for (j = i.value().begin(); j != j_end; ++j) {
            EventList * ev = *j;
            ev->clear();
            ev->m_data.squeeze();
            ev->m_data2.squeeze();
            ev->m_time.squeeze();
            delete ev;
        }
    }

    s_events_loaded = false;
    eventlist.clear();
    eventlist.squeeze();
}

bool Session::enabled(bool realValues) const
{
    if (p_profile->cpap->clinicalMode() && !realValues) return true;
    return s_enabled;
}

void Session::setEnabled(bool b)
{
    s_enabled = b;
    // not so simple.. we have to invalidate the hours cache in the day record..

    Day * day = p_profile->findSessionDay(this);
    if (day) {
        day->invalidate();
    }
}

QString Session::eventFile() const
{
    return s_machine->getEventsPath()+toHexid(s_session)+".001";
}

//const int max_pack_size=128;
bool Session::OpenEvents(bool debug)
{
    if (s_events_loaded) {
        return true;
    }

    s_events_loaded = eventlist.size() > 0;

    if (s_events_loaded) {
        return true;
    }


    QString filename = eventFile();
#ifdef DEBUG_EVENTS
    if (debug) qDebug() << "Loading" << s_machine->loaderName().toLocal8Bit().data() << "Events:" << filename.toLocal8Bit().data();
#endif
    bool b = LoadEvents(filename,debug);

    if ( ! b) {
        if (debug) qWarning() << "Error Loading Events" << filename;
        return false;
    }

    return s_events_loaded = true;
}

bool Session::Destroy()
{
    QDir dir;
    QString base;
    base=toHexid(s_session);

    QString summaryfile = s_machine->getSummariesPath() + base + ".000";
    QString eventfile = s_machine->getEventsPath() + base + ".001";
    if ( ! dir.remove(summaryfile)) {
        qWarning() << "Could not delete" << summaryfile;
    }
    if ( ! dir.remove(eventfile)) {
        qWarning() << "Could not delete" << eventfile;
    }

    return s_machine->unlinkSession(this);
}

bool Session::Store(QString path)
// Storing Session Data in our format
// {DataDir}/{MachineID}/{SessionID}.{ext}
{
    QDir dir(path);

    if ( ! dir.exists(path)) {
        dir.mkpath(path);
    }

    //qDebug() << "Storing Session: " << base;
    bool a;

    a = StoreSummary(); // if actually has events

    //qDebug() << " Summary done";
    if (eventlist.size() > 0) {
        StoreEvents();
    } else { // who cares..
        //qDebug() << "Trying to save empty events file";
    }

    //qDebug() << " Events done";
    s_changed = false;
    s_events_loaded = true;

    //} else {
    //    qDebug() << "Session::Store() No event data saved" << s_session;
    //}

    return a;
}

//QDataStream & operator<<(QDataStream & out, const Session & session)
//{
//    session.StoreSummaryData(out);
//    return out;
//}
//
//void Session::StoreSummaryData(QDataStream & out) const
//{
//    out << summary_version;
//    out << (quint32)s_session;
//    out << s_first;  // Session Start Time
//    out << s_last;   // Duration of sesion in seconds.
//
//    out << settings;
//    out << m_cnt;
//    out << m_sum;
//    out << m_avg;
//    out << m_wavg;
//
//    out << m_min;
//    out << m_max;
//
//    out << m_physmin;
//    out << m_physmax;
//
//    out << m_cph;
//    out << m_sph;
//
//    out << m_firstchan;
//    out << m_lastchan;
//
//    out << m_valuesummary;
//    out << m_timesummary;
//
//    out << m_gain;
//
//    out << m_availableChannels;
//
//    out << m_timeAboveTheshold;
//    out << m_upperThreshold;
//    out << m_timeBelowTheshold;
//    out << m_lowerThreshold;
//
//    out << s_summaryOnly;
//}
//
//QDataStream & operator>>(QDataStream & in, Session & session)
//{
//    session.LoadSummaryData(in);
//    return in;
//}
//
//
//void Session::LoadSummaryData(QDataStream & in)
//{
//    quint16 version;
//    in >> version;
//
//    quint32 t32;
//    in >> t32;      // Sessionid;
//    s_session = t32;
//
//    in >> s_first;  // Start time
//    in >> s_last;   // Duration
//
//    in >> settings;
//    in >> m_cnt;
//    in >> m_sum;
//    in >> m_avg;
//    in >> m_wavg;
//
//    in >> m_min;
//    in >> m_max;
//
//    in >> m_physmin;
//    in >> m_physmax;
//
//    in >> m_cph;
//    in >> m_sph;
//    in >> m_firstchan;
//    in >> m_lastchan;
//
//    in >> m_valuesummary;
//    in >> m_timesummary;
//
//    in >> m_gain;
//
//    in >> m_availableChannels;
//    in >> m_timeAboveTheshold;
//    in >> m_upperThreshold;
//    in >> m_timeBelowTheshold;
//    in >> m_lowerThreshold;
//
//    in >> s_summaryOnly;
//
//    s_enabled = 1;
//}

QDataStream & operator>>(QDataStream & in, SessionSlice & slice)
{
    in >> slice.start;
    quint32 length;
    in >> length;
    slice.end = slice.start + length;

    quint16 i;
    in >> i;
    slice.status = (SliceStatus)i;
    return in;
}
QDataStream & operator<<(QDataStream & out, const SessionSlice & slice)
{
    out << slice.start;
    quint32 length = slice.end - slice.start;
    out << length;
    out << (quint16)slice.status;
    return out;
}

bool Session::StoreSummary()
{
    if (s_first == 0) {
        qWarning() << "Session::StoreSummary discarding session" << s_session
                 << "["+QDateTime::fromSecsSinceEpoch(s_session).toString("MMM dd, yyyy hh:mm:ss")+"]"
                 << "from machine" << machine()->serial() << "with first=0";
        return false;
    }

    QString filename = s_machine->getSummariesPath() + toHexid(s_session) + ".000" ;

    QFile file(filename);
    if (!file.open(QIODevice::WriteOnly)) {
        QDir dir;
        dir.mkpath(s_machine->getSummariesPath());

        if (!file.open(QIODevice::WriteOnly)) {
//            qWarning() << "Summary open for writing failed" << "error code" << file.error() << file.errorString();
            qWarning() << "Could not open summary" << filename << "for writing, error code" << file.error() << file.errorString();
            return false;
        }
    }

    QDataStream out(&file);
    out.setVersion(QDataStream::Qt_4_6);
    out.setByteOrder(QDataStream::LittleEndian);

    out << (quint32)magic;
    out << (quint16)summary_version;
    out << (quint16)filetype_summary;
    out << (quint32)s_machine->id();

    out << (quint32)s_session;
    out << s_first;  // Session Start Time
    out << s_last;   // Duration of sesion in seconds.
    //out << (quint16)settings.size();

    out << settings;
    out << m_cnt;

    out << m_sum;
    out << m_avg;
    out << m_wavg;

    out << m_min;
    out << m_max;
    out << m_physmin;
    out << m_physmax;
    out << m_cph;
    out << m_sph;
    out << m_firstchan;
    out << m_lastchan;

    // <- 8
    out << m_valuesummary;
    out << m_timesummary;
    // 8 ->

    // <- 9
    out << m_gain;
    // 9 ->

    // <- 15
    out << m_availableChannels;
    out << m_timeAboveTheshold;
    out << m_upperThreshold;
    out << m_timeBelowTheshold;
    out << m_lowerThreshold;

    out << s_summaryOnly;
    // 13 ->

    out << s_noSettings; // 18

    out << m_slices;

    file.close();
    return true;
}

SpeedCheck scLoad(50); // Keep outside function so number of messages is limited
bool Session::LoadSummary(bool debug)
{
//    static int sumcnt = 0;

    scLoad.restart();
    if (s_summary_loaded) return true;
    QString filename = s_machine->getSummariesPath() + toHexid(s_session) + ".000";

    scLoad.setMsg("Session::LoadSummary processing " + filename);

    if (filename.isEmpty()) {
        if (debug) qDebug() << "Empty summary filename";
        return false;
    }

    QFile file(filename);

    if (!file.open(QIODevice::ReadOnly)) {
        if (debug) qWarning() << "Could not open summary file" << filename << "for reading, error code" << file.error() << file.errorString();
        return false;
    }

//    qDebug() << "Loading" << s_machine->loaderName() << "Summary" << filename << sumcnt++;

    QDataStream in(&file);
    in.setVersion(QDataStream::Qt_4_6);
    in.setByteOrder(QDataStream::LittleEndian);

    quint32 t32;
    quint16 t16;

    //QHash<ChannelID,MCDataType> mctype;
    //QVector<ChannelID> mcorder;
    in >> t32;

    if (t32 != magic) {
        if (debug) qDebug() << "Wrong magic number in " << filename;
        return false;
    }

    quint16 version;
    in >> version;      // DB Version

    if (version < 6) {
        //throw OldDBVersion();
        qWarning() << "Old dbversion " << version <<
                   "summary file.. Sorry, you need to purge and reimport";
        return false;
    }

    in >> t16;      // File Type

    if (t16 != filetype_summary) {
        qDebug() << "Wrong file type"; //wrong file type
        return false;
    }


    quint32 ts32;
    in >> ts32;      // MachineID (dont need this result)

    bool upgrade = false;
    if ( ts32 != s_machine->id()) {
        upgrade = true;
        qWarning() << "Machine ID does not match in" << filename <<
                   " I will try to load anyway in case you know what your doing.";
    }

    in >> t32;      // Sessionid;
    s_session = t32;

    in >> s_first;  // Start time
    in >> s_last;   // Duration // (16bit==Limited to 18 hours)

    QHash<ChannelID, EventDataType> cruft;

    if (version < 7) {
        // This code is deprecated.. just here incase anyone tries anything crazy...
        QHash<QString, QVariant> v1;
        in >> v1;
        settings.clear();
        ChannelID code;

        for (QHash<QString, QVariant>::iterator i = v1.begin(); i != v1.end(); i++) {
            code = schema::channel[i.key()].id();
            settings[code] = i.value();
        }

        QHash<QString, int> zcnt;
        in >> zcnt;
        for (QHash<QString, int>::iterator i = zcnt.begin(); i != zcnt.end(); i++) {
            code = schema::channel[i.key()].id();
            m_cnt[code] = i.value();
        }

        QHash<QString, double> zsum;
        in >> zsum;

        for (QHash<QString, double>::iterator i = zsum.begin(); i != zsum.end(); i++) {
            code = schema::channel[i.key()].id();
            m_sum[code] = i.value();
        }

        QHash<QString, EventDataType> ztmp;
        in >> ztmp; // avg

        for (QHash<QString, EventDataType>::iterator i = ztmp.begin(); i != ztmp.end(); i++) {
            code = schema::channel[i.key()].id();
            m_avg[code] = i.value();
        }

        ztmp.clear();
        in >> ztmp; // wavg

        for (QHash<QString, EventDataType>::iterator i = ztmp.begin(); i != ztmp.end(); i++) {
            code = schema::channel[i.key()].id();
            m_wavg[code] = i.value();
        }

        ztmp.clear();
        in >> ztmp; // 90p
        ztmp.clear();
        in >> ztmp; // min

        for (QHash<QString, EventDataType>::iterator i = ztmp.begin(); i != ztmp.end(); i++) {
            code = schema::channel[i.key()].id();
            m_min[code] = i.value();
        }

        ztmp.clear();
        in >> ztmp; // max

        for (QHash<QString, EventDataType>::iterator i = ztmp.begin(); i != ztmp.end(); i++) {
            code = schema::channel[i.key()].id();
            m_max[code] = i.value();
        }

        ztmp.clear();
        in >> ztmp; // cph

        for (QHash<QString, EventDataType>::iterator i = ztmp.begin(); i != ztmp.end(); i++) {
            code = schema::channel[i.key()].id();
            m_cph[code] = i.value();
        }

        ztmp.clear();
        in >> ztmp; // sph

        for (QHash<QString, EventDataType>::iterator i = ztmp.begin(); i != ztmp.end(); i++) {
            code = schema::channel[i.key()].id();
            m_sph[code] = i.value();
        }

        QHash<QString, quint64> ztim;
        in >> ztim; //firstchan

        for (QHash<QString, quint64>::iterator i = ztim.begin(); i != ztim.end(); i++) {
            code = schema::channel[i.key()].id();
            m_firstchan[code] = i.value();
        }

        ztim.clear();
        in >> ztim; // lastchan

        for (QHash<QString, quint64>::iterator i = ztim.begin(); i != ztim.end(); i++) {
            code = schema::channel[i.key()].id();
            m_lastchan[code] = i.value();
        }

        //SetChanged(true);
    } else {
        // version > 7

        in >> settings;
        if (version < 13) {
            QHash<ChannelID, int> cnt2;
            in >> cnt2;

            QHash<ChannelID, int>::iterator it;

            for (it = cnt2.begin(); it != cnt2.end(); ++it) {
                m_cnt[it.key()] = it.value();
            }
        } else {
            in >> m_cnt;
        }
        in >> m_sum;
        in >> m_avg;
        in >> m_wavg;

        if (version < 11) {
            cruft.clear();
            in >> cruft; // 90%

            if (version >= 10) {
                cruft.clear();
                in >> cruft;// med
                cruft.clear();
                in >> cruft; //p95
            }
        }

        in >> m_min;
        in >> m_max;

        // Added 24/10/2013 by MW to support physical graph min/max values
        if (version >= 12) {
            in >> m_physmin;
            in >> m_physmax;
        }

        in >> m_cph;
        in >> m_sph;
        in >> m_firstchan;
        in >> m_lastchan;

        if (version >= 8) {
            in >> m_valuesummary;
            in >> m_timesummary;

            if (version >= 9) {
                in >> m_gain;
            }
        }

        // screwed up with version 14
        if (version >= 15) {
            in >> m_availableChannels;
            in >> m_timeAboveTheshold;
            in >> m_upperThreshold;
            in >> m_timeBelowTheshold;
            in >> m_lowerThreshold;
        } // else this is ugly.. forced device database upgrade will solve it though.

        if (version == 13) {
            QHash<ChannelID, QVariant>::iterator it = settings.find(CPAP_SummaryOnly);
            if (it != settings.end()) {
                s_summaryOnly = (*it).toBool();
            } else s_summaryOnly = false;
        } else if (version > 13) {
            in >> s_summaryOnly;
        }
        if (version >= 18) {
            in >> s_noSettings;
//            qDebug() << "Session::LoadSummary" << s_session << "["
//                     << QDateTime::fromSecsSinceEpoch(s_session).toString("MM/dd/yyyy hh:mm:ss")
//                     << "] s_noSettings" << s_noSettings << "size" << settings.size();
        } else {
            s_noSettings = (settings.size() == 0);
        }

        if (version == 16) {
            QList<SessionSlice> slices;
            in >> slices;
            m_slices.clear();
            for (int i=0;i<slices.size(); ++i) {
                m_slices.append(slices[i]);
            }
        } else if (version >= 17) {
            in >> m_slices;
        }
    }

    // not really a good idea to do this... should flag and do a reindex
    if (upgrade || (version < summary_version)) {

        qDebug() << "Upgrading Summary file to version" << summary_version;
        if (!s_summaryOnly) {
            OpenEvents();
            UpdateSummaries();
            TrashEvents();
        } else {
            // summary only upgrades go here.
        }
        StoreSummary();
    }

    scLoad.check();

    s_summary_loaded = true;
    return true;
}

const quint16 compress_method = 1;

bool Session::StoreEvents()
{
    QString path = s_machine->getEventsPath();
    QDir dir;
    dir.mkpath(path);
    QString filename = path+ toHexid(s_session) +".001";

    QFile file(filename);
    if (!file.open(QIODevice::WriteOnly)) {
        qWarning() << "Could not open events file" << filename << "for writing, error code" << file.error() << file.errorString();
        return false;
    }

    QByteArray headerbytes;
    QDataStream header(&headerbytes, QIODevice::WriteOnly);
    header.setVersion(QDataStream::Qt_4_6);
    header.setByteOrder(QDataStream::LittleEndian);

    header << (quint32)magic;      // New Magic Number
    header << (quint16)events_version; // File Version
    header << (quint16)filetype_data;  // File type 1 == Event
    header << (quint32)s_machine->id();// Device Type
    header << (quint32)s_session;      // This session's ID
    header << s_first;
    header << s_last;

    quint16 compress = 0;

    if (p_profile->session->compressSessionData()) {
        compress = compress_method;
    }

    header << (quint16)compress;

    header << (quint16)s_machine->type();// Device Type

    QByteArray databytes;
    QDataStream out(&databytes, QIODevice::WriteOnly);
    out.setVersion(QDataStream::Qt_4_6);
    out.setByteOrder(QDataStream::LittleEndian);

    out << (qint16)eventlist.size(); // Number of event categories

    QHash<ChannelID, QVector<EventList *> >::iterator i;
    QHash<ChannelID, QVector<EventList *> >::iterator i_end=eventlist.end();

    qint16 ev_size;

    for (i = eventlist.begin(); i != i_end; i++) {
        ev_size=i.value().size();

        out << i.key(); // ChannelID
        out << (qint16)ev_size;


        for (int j = 0; j < ev_size; j++) {
            EventList &e = *i.value()[j];
            out << e.first();
            out << e.last();
            out << (qint32)e.count();
            out << (qint8)e.type();
            out << e.rate();
            out << e.gain();
            out << e.offset();
            out << e.Min();
            out << e.Max();
            out << e.dimension();
            out << e.hasSecondField();

            if (e.hasSecondField()) {
                out << e.min2();
                out << e.max2();
            }
        }
    }
    for (i = eventlist.begin(); i != i_end; i++) {
        ev_size=i.value().size();

        for (int j = 0; j < ev_size; j++) {
            EventList &e = *i.value()[j];
            // ****** This is assuming little endian ******

            // Store the raw event list data in EventStoreType (16bit short)
            EventStoreType *ptr = e.m_data.data();
            out.writeRawData((char *)ptr, e.count() << 1);

            //*** Don't delete these comments ***
            //            for (quint32 c=0;c<e.count();c++) {
            //                out << *ptr++;//e.raw(c);
            //            }

            // Store the second field, only if there
            if (e.hasSecondField()) {
                ptr = e.m_data2.data();
                out.writeRawData((char *)ptr, e.count() << 1);
                //*** Don't delete these comments ***
                //                for (quint32 c=0;c<e.count();c++) {
                //                    out << *ptr++; //e.raw2(c);
                //                }
            }

            // Store the time delta fields for non-waveform EventLists
            if (e.type() != EVL_Waveform) {
                quint32 *tptr = e.m_time.data();
                out.writeRawData((char *)tptr, e.count() << 2);
                //*** Don't delete these comments ***
                //                for (quint32 c=0;c<e.count();c++) {
                //                    out << *tptr++; //e.getTime()[c];
                //                }
            }
        }
    }

    qint32 datasize = databytes.size();

    // Checksum the _uncompressed_ data
    quint16 chk = 0;

    if (compress) {
        // This checksum is hideously slow.. only using during compression, and not sure I should at all :-/
        #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
            chk = qChecksum(databytes.data(), databytes.size());
        #else
            chk = qChecksum(QByteArrayView(databytes));
        #endif
    }

    header << datasize;
    header << chk;

    QByteArray data;

    if (compress > 0) {
        data = qCompress(databytes);
    } else {
        data = databytes;
    }

    file.write(headerbytes);
    file.write(data);
    file.close();
    return true;
}

bool Session::LoadEvents(QString filename, bool debug)
{
    quint32 magicnum, machid, sessid;
    quint16 version, type, crc16, machtype, compmethod;
    quint8 t8;
    qint32 datasize;

    if (filename.isEmpty()) {
        qDebug() << "Session::LoadEvents() Filename is empty";
        return false;
    }

    QFile file(filename);

    if ( ! file.open(QIODevice::ReadOnly)) {
//        qDebug() << "No Event/Waveform data available for" << s_session;
        if (debug) qWarning() << "No Event/Waveform data available for" << s_session << "filename" << filename << "error code" << file.error() << file.errorString();
        return false;
    }

    QByteArray headerbytes = file.read(42);

    QDataStream header(headerbytes);
    header.setVersion(QDataStream::Qt_4_6);
    header.setByteOrder(QDataStream::LittleEndian);

    header >> magicnum;         // Magic Number (quint32)
    header >> version;          // Version (quint16)
    header >> type;             // File type (quint16)
    header >> machid;           // Device ID (quint32)
    header >> sessid;           //(quint32)
    header >> s_first;          //(qint64)
    header >> s_last;           //(qint64)

#ifdef DEBUG_EVENTS
    qDebug() << "Session ID" << sessid << "Start Time" << QDateTime::fromMSecsSinceEpoch(s_first);
#endif

    if (type != filetype_data) {
        qDebug() << "Wrong File Type in " << filename;
        return false;
    }

    if (magicnum != magic) {
        qWarning() << "Wrong Magic number in " << filename;
        return false;
    }

    if (version < 6) {  // prior to version 6 is too old to deal with
        qDebug() << "Old File Version, can't open file";
        return false;
    }

    if (version < 10) {
        file.seek(32);
    } else {
        header >> compmethod;   // Compression Method (quint16)
        header >> machtype;     // Device Type (quint16)
        header >> datasize;     // Size of Uncompressed Data (quint32)
        header >> crc16;        // CRC16 of Uncompressed Data (quint16)
    }

    QByteArray databytes, temp = file.readAll();
    file.close();

    if (version >= 10) {
        if (compmethod > 0) {
            databytes = qUncompress(temp);

            if (!s_evchecksum_checked) {
                if (databytes.size() != datasize) {
                    qDebug() << "File" << filename << "has returned wrong datasize";
                    return false;
                }

                quint16 crc = 0;
                #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
                    crc = qChecksum(databytes.data(), databytes.size());
                #else
                    crc = qChecksum(QByteArrayView(databytes));
                #endif

                if (crc != crc16) {
                    qDebug() << "CRC Doesn't match in" << filename;
                    return false;
                }

                s_evchecksum_checked = true;
            }
        } else {
            databytes = temp;
        }
    } else { databytes = temp; }

    QDataStream in(databytes);
    in.setVersion(QDataStream::Qt_4_6);
    in.setByteOrder(QDataStream::LittleEndian);

    qint16 mcsize;
    in >> mcsize;   // number of Device Code lists
#ifdef DEBUG_EVENTS
    qDebug() << "Number of Channels" << mcsize;
#endif

    ChannelID code;
    qint64 ts1, ts2;
    qint32 evcount;
    EventListType elt;
    EventDataType rate, gain, offset, mn, mx;
    qint16 size2;
    QVector<ChannelID> mcorder;
    QVector<qint16> sizevec;
    QString dim;

    for (int i = 0; i < mcsize; i++) {
        if (version < 8) {
            QString txt;
            in >> txt;
            code = schema::channel[txt].id();
        } else {
            in >> code;
        }

        mcorder.push_back(code);
        in >> size2;
        sizevec.push_back(size2);
#ifdef DEBUG_EVENTS
        qDebug() << "For Channel (hex)" << QString::number(code, 16) << "there are" << size2 << "EventLists";
#endif

        for (int j = 0; j < size2; j++) {
            in >> ts1;
            in >> ts2;
#ifdef DEBUG_EVENTS
            qDebug() << "Start:" << QDateTime::fromMSecsSinceEpoch(ts1).toString() <<
                        "Finish:" << QDateTime::fromMSecsSinceEpoch(ts2).toString();
#endif
            in >> evcount;
            in >> t8;
            elt = (EventListType)t8;
            in >> rate;
            in >> gain;
            in >> offset;
            in >> mn;
            in >> mx;
            in >> dim;
            bool second_field = false;

            if (version >= 7) { // version 7 added this field
                in >> second_field;
            }

            EventList *elist = AddEventList(code, elt, gain, offset, mn, mx, rate, second_field);
            elist->setDimension(dim);

            //eventlist[code].push_back(elist);
            elist->m_count = evcount;
            elist->m_first = ts1;
            elist->m_last = ts2;

            if (second_field) {
                EventDataType min, max;
                in >> min;
                in >> max;
                elist->setMin2(min);
                elist->setMax2(max);
            }
        }
    }

    //EventStoreType t;     // qint16
    //quint32 x;

    for (int i = 0; i < mcsize; i++) {
        code = mcorder[i];
        size2 = sizevec[i];

        for (int j = 0; j < size2; j++) {
            EventList &evec = *eventlist[code][j];
            evec.m_data.resize(evec.m_count);
            EventStoreType *ptr = evec.m_data.data();

            // ****** This is assuming little endian ******

            in.readRawData((char *)ptr, evec.m_count << 1);

            //*** Don't delete these comments ***
            //*** They explain what the above ReadRawData is doing!
            //            for (quint32 c=0;c<evec.m_count;c++) {
            //                in >> t;
            //                *ptr++=t;
            //            }
            if (evec.hasSecondField()) {
                evec.m_data2.resize(evec.m_count);
                ptr = evec.m_data2.data();

                in.readRawData((char *)ptr, evec.m_count << 1);
                //*** Don't delete these comments ***
                //*** They explain what the above ReadRawData is doing!
                //                for (quint32 c=0;c<evec.m_count;c++) {
                //                    in >> t;
                //                    *ptr++=t;
                //                }
            }

            if (evec.type() != EVL_Waveform) {
                evec.m_time.resize(evec.m_count);
                quint32 *tptr = evec.m_time.data();

                in.readRawData((char *)tptr, evec.m_count << 2);
                //*** Don't delete these comments ***
                //                for (quint32 c=0;c<evec.m_count;c++) {
                //                    in >> x;
                //                    *tptr++=x;
                //                }
            }
        }
    }

    if (version < events_version) {
        qDebug() << "Upgrading Events file" << filename << "to version" << events_version;
        UpdateSummaries();
        StoreEvents();
    }

    return true;
}

void Session::destroyEvent(ChannelID code)
{
    QHash<ChannelID, QVector<EventList *> >::iterator it = eventlist.find(code);

    if (it != eventlist.end()) {
        for (int i = 0; i < it.value().size(); i++) {
            delete it.value()[i];
        }

        eventlist.erase(it);
    }

    m_gain.erase(m_gain.find(code));
    m_firstchan.erase(m_firstchan.find(code));
    m_lastchan.erase(m_lastchan.find(code));
    m_sph.erase(m_sph.find(code));
    m_cph.erase(m_cph.find(code));
    m_min.erase(m_min.find(code));
    m_max.erase(m_max.find(code));
    m_avg.erase(m_avg.find(code));
    m_wavg.erase(m_wavg.find(code));
    m_sum.erase(m_sum.find(code));
    m_cnt.erase(m_cnt.find(code));
    m_valuesummary.erase(m_valuesummary.find(code));
    m_timesummary.erase(m_timesummary.find(code));
    // does not trash settings..
}

// TODO: The below assumes values are held for their duration. This does not properly handle
// CPAP_PressureSet/EPAPSet/IPAPSet or other interpolated channels. The proper "value" held
// for any given duration is the average of the starting and ending values, for the duration
// between them.
void Session::updateCountSummary(ChannelID code)
{
    QHash<ChannelID, QVector<EventList *> >::iterator ev = eventlist.find(code);

    if (ev == eventlist.end()) {
        qDebug() << "No events for channel (hex)" << QString::number(code, 16);
        return;
    }

    QHash<ChannelID, QHash<EventStoreType, EventStoreType> >::iterator vs = m_valuesummary.find(code);

    if (vs != m_valuesummary.end()) { // already calculated?
        return;
    }

    QHash<EventStoreType, EventStoreType> valsum;
    QHash<EventStoreType, quint32> timesum;

    //QHash<EventStoreType, EventStoreType>::iterator it;
    //QHash<EventStoreType, EventStoreType>::iterator valsum_end;

    EventDataType raw, lastraw = 0;
    qint64 start, time, lasttime = 0;
    qint32 len, cnt;
    quint32 *tptr;
    EventStoreType *dptr, * eptr;

    int ev_size=ev.value().size();
    for (int i = 0; i < ev_size; i++) {
        EventList &e = *(ev.value()[i]);
        start = e.first();
        cnt = e.count();
        dptr = e.rawData();
        eptr = dptr + cnt;

        EventDataType rate = 0;

        m_gain[code] = e.gain();

        if (e.type() == EVL_Event) {
            tptr = e.rawTime();
            // The last event event raw time is never updated with the len to the next.
            // this is true for multiple samples or for a single sample.
            // a single sample is the first as well as the last sample.
            // so a single sample should work.
            // the iBreeze loader had snore events with a single snore sample for a session.
            // which triggered this investigation.

            // CRASH DEBUG DESCRIPTION.
            // Why this occurs only for QT6 and not QT5 is not known at this time
            // This crash occurs when an empty event list is created for an event.
            // and another event list is created for that event then the empty event list
            // will trigger the crash.
            // every time an event is added to an event list a counter is incremented.
            // if the count is zero then tptr is a null pointer and triggers the crash.
            // when the count is zero then just continue to process the next event list.
            if (cnt==0) {
                #if 0
                if (tptr) {
                    DEBUGCI NAME(code) Q((void*)&e) Q((void*)tptr) Q((void*)dptr) Q(start) Q(cnt) Q(ev_size) ;
                }
                #endif
                continue;
            }
            #if defined(FIX_FOR_SINGLE_EVENT)
            lastraw = *dptr;
            lasttime = start + *tptr;
            #else
            lastraw = *dptr++;
            lasttime = start + *tptr++;
            #endif
            // Event version

            for (; dptr < eptr; dptr++) {
                time = start + *tptr++;
                raw = *dptr;

                valsum[raw]++;

                // elapsed time in seconds since last event occurred
                len = (time - lasttime) / 1000L;

                timesum[lastraw] += len;

                lastraw = raw;
                lasttime = time;
            } 
        } else {
            // Waveform version, first just count
            for (; dptr < eptr; dptr++) {
                raw = *dptr;
                valsum[raw]++;
            }

            // Then process the list of values, time is simply (rate * count)
            rate = e.rate();
            EventDataType t;

            QHash<EventStoreType, EventStoreType>::iterator it = valsum.begin();
            QHash<EventStoreType, EventStoreType>::iterator valsum_end = valsum.end();

            for (; it != valsum_end; ++it) {
                t = EventDataType(it.value()) * rate;
                timesum[it.key()] += t;
            }
        }
    }

    if ( valsum.size() == 0) {  // no value summary for this channel
        using namespace schema;
        Channel *ch_p = channel.channels[code];
        if (  ! ch_p->isNull() ) {                      // the channel was found in the channel list
            if ( ((ch_p->type() & (FLAG|SPAN|MINOR_FLAG)) == 0) ) {  // the channel is not a flag or span type
                qDebug() << "No valuesummary for channel " << ch_p->label() <<  " " << QDateTime::fromMSecsSinceEpoch( realFirst()).toString() ;    // so tell about missing summary
            }
        } else {
            // This channel wasn't added to the channel list, so we can't check its type
            qDebug() << "No valuesummary for channel (hex)" << QString::number(code, 16);
        }
        return;
    }

    m_valuesummary[code] = valsum;
    m_timesummary[code] = timesum;
}

void Session::UpdateSummaries()
{
    ChannelID id;

    // Generate that AHI per hour graph in daily view.
    calcAHIGraph(this);

    // Calculates RespRate and related waveforms (Tv, MV, Te, Ti) if missing
    calcRespRate(this);

    // Generate unintentional leaks if not present
    calcLeaks(this);

    // Flag the Large Leaks if unintentional leaks is available, and no LargeLeaks weren't flagged by the device already.
    flagLargeLeaks(this);

    calcSPO2Drop(this);
    calcPulseChange(this);

    QHash<ChannelID, QVector<EventList *> >::iterator c = eventlist.begin();
    QHash<ChannelID, QVector<EventList *> >::iterator ev_end = eventlist.end();

    m_availableChannels.clear();

    for (; c != ev_end; c++) {
        id = c.key();
        m_availableChannels.push_back(id);

        schema::ChanType ctype = schema::channel[id].type();
        if (ctype != schema::SETTING) {
            //sum(id); // avg calculates this and cnt.
            if (c.value().size() > 0) {
                EventList *el = c.value()[0];
                EventDataType gain = el->gain();
                m_gain[id] = gain;
            }

            if (!((id == CPAP_FlowRate) || (id == CPAP_MaskPressureHi) || (id == CPAP_RespEvent)
                    || (id == CPAP_MaskPressure))) {
                updateCountSummary(id);
            }

            Min(id);
            Max(id);
            count(id);
            last(id);
            first(id);

            if (((id == CPAP_FlowRate)
                 || (id == CPAP_MaskPressureHi)
                 || (id == CPAP_RespEvent)
                 || (id == CPAP_MaskPressure))) {
                continue;
            }

            cph(id);
            sph(id);
            avg(id);
            wavg(id);
        }
    }
    timeAboveThreshold(CPAP_Leak, p_profile->cpap->leakRedline());

    s_machine->updateChannels(this);
}

EventDataType Session::SearchValue(ChannelID code, qint64 time, bool square)
{
    qint64 drift = qint64(p_profile->cpap->clockDrift()) * 1000L;
    // Address clock drift for CPAP so correct value is displayed
    if (s_machine->type() == MT_CPAP) {
        time -= drift;
    }
    qint64 t1, t2, start;
    QHash<ChannelID, QVector<EventList *> >::iterator it;
    it = eventlist.find(code);
    quint32 *tptr;
    int cnt;

    EventDataType a,b,c,d,e;
    if (it != eventlist.end()) {
        int el_size=it.value().size();
        for (int i = 0; i < el_size; i++)  {
            EventList *el = it.value()[i];
            if ((time >= el->first()) && (time < el->last())) {
                cnt = el->count();

                if (el->type() == EVL_Waveform) {
                    qint64 tt = time - el->first();

                    double i = tt / el->rate();
                    if (i> cnt) {
                        qWarning() << "Session" << session() << "time bounds are broken.. There is a fault in the" << machine()->loaderName().toLocal8Bit().data() << "loader";
                        return 0;
                    }

                    int i1 = int(floor(i));
                    int i2 = int(ceil(i));

                    a = el->data(i1);

                    // Don't interpolate if next data point is past end or on exact data point
                    if (i2 >= cnt || i1 == i2) { return a; }

                    qint64 t1 = i1 * el->rate();
                    qint64 t2 = i2 * el->rate();

                    c = EventDataType(t2 - t1);
                    // Don't interpolate if t2-t1==0 (should be caught by i1==i2 above)
                    if (c == 0) return a;
                    d = EventDataType(t2 - tt);

                    e = d/c;
                    b = el->data(i2);

                    return b + ((a-b) * e);

                } else {
                    start = el->first();
                    tptr = el->rawTime();
                    // TODO: square plots need fixing
                    if (square) {
                        for (int j = 0; j < cnt-1; ++j) {
                            tptr++;
                            t2 = start + *tptr;
                            if (t2 > time) {
                                return el->data(j);
                            }
                        }
                    } else {
                        for (int j = 0; j < cnt-1; ++j) {
                            tptr++;
                            t2 = start + *tptr;
                            if (t2 > time) {
                                tptr--;
                                t1 = start + *tptr;
                                c = EventDataType(t2 - t1);
                                d = EventDataType(t2 - time);
                                e = d/c;
                                a = el->data(j);
                                b = el->data(j+1);
                                if (a == b) {
                                    return a;
                                } else {
                                    return b + ((a-b) * e);
                                }
                            }
                        }
                    }
                }
            }
        }
    }

    return 0;
}

QString Session::dimension(ChannelID id)
{
    // Cheat for now
    return schema::channel[id].units();
}

EventDataType Session::Min(ChannelID id)
{
    QHash<ChannelID, EventDataType>::iterator i = m_min.find(id);

    if (i != m_min.end()) {
        return i.value();
    }

    QHash<ChannelID, QVector<EventList *> >::iterator j = eventlist.find(id);

    if (j == eventlist.end()) {
        m_min[id] = 0;
        return 0;
    }

    QVector<EventList *> &evec = j.value();

    bool first = true;
    EventDataType min = 0, t1;

    int evec_size = evec.size();

    for (int i = 0; i < evec_size; ++i) {
        if (evec[i]->count() != 0) {
            t1 = evec[i]->Min();

            if ((t1 == 0) && (t1 == evec[i]->Max())) { continue; }

            if (first) {
                min = t1;
                first = false;
            } else {
                if (min > t1) { min = t1; }
            }
        }
    }

    m_min[id] = min;
    return min;
}

EventDataType Session::Max(ChannelID id)
{
    QHash<ChannelID, EventDataType>::iterator i = m_max.find(id);

    if (i != m_max.end()) {
        return i.value();
    }

    QHash<ChannelID, QVector<EventList *> >::iterator j = eventlist.find(id);

    if (j == eventlist.end()) {
        m_max[id] = 0;
        return 0;
    }

    QVector<EventList *> &evec = j.value();

    bool first = true;
    EventDataType max = 0, t1;

    int evec_size=evec.size();

    for (int i = 0; i < evec_size; ++i) {
        if (evec.at(i)->count() != 0) {
            t1 = evec.at(i)->Max();

            if (t1 == 0 && t1 == evec.at(i)->Min()) { continue; }

            if (first) {
                max = t1;
                first = false;
            } else {
                if (max < t1) { max = t1; }
            }
        }
    }

    m_max[id] = max;
    return max;
}

////
EventDataType Session::physMin(ChannelID id)
{
    QHash<ChannelID, EventDataType>::iterator i = m_physmin.find(id);

    if (i != m_physmin.end()) {
        return i.value();
    }

    QHash<ChannelID, QVector<EventList *> >::iterator j = eventlist.find(id);

    if (j == eventlist.end()) {
        m_physmin[id] = 0;
        return 0;
    }

    EventDataType min = floor(Min(id));
    m_physmin[id] = min;
    return min;
}

EventDataType Session::physMax(ChannelID id)
{
    QHash<ChannelID, EventDataType>::iterator i = m_physmax.find(id);

    if (i != m_physmax.end()) {
        return i.value();
    }

    QHash<ChannelID, QVector<EventList *> >::iterator j = eventlist.find(id);

    if (j == eventlist.end()) {
        m_physmax[id] = 0;
        return 0;
    }

    EventDataType max = ceil(Max(id) + 0.5);
    m_physmax[id] = max;
    return max;
}


qint64 Session::first(ChannelID id)
{
    qint64 drift = qint64(p_profile->cpap->clockDrift()) * 1000L;
    qint64 tmp;
    QHash<ChannelID, quint64>::iterator i = m_firstchan.find(id);

    if (i != m_firstchan.end()) {
        tmp = i.value();

        if (s_machine->type() == MT_CPAP) {
            tmp += drift;
        }

        return tmp;
    }

    QHash<ChannelID, QVector<EventList *> >::iterator j = eventlist.find(id);

    if (j == eventlist.end()) {
        return 0;
    }

    QVector<EventList *> &evec = j.value();

    bool first = true;
    qint64 min = 0, t1;

    int evec_size=evec.size();
    for (int i = 0; i < evec_size; ++i) {
        t1 = evec[i]->first();

        if (first) {
            min = t1;
            first = false;
        } else {
            if (min > t1) { min = t1; }
        }
    }

    m_firstchan[id] = min;

    if (s_machine->type() == MT_CPAP) {
        min += drift;
    }

    return min;
}
qint64 Session::last(ChannelID id)
{
    qint64 drift = qint64(p_profile->cpap->clockDrift()) * 1000L;
    qint64 tmp;
    QHash<ChannelID, quint64>::iterator i = m_lastchan.find(id);

    if (i != m_lastchan.end()) {
        tmp = i.value();

        if (s_machine->type() == MT_CPAP) {
            tmp += drift;
        }

        return tmp;
    }

    QHash<ChannelID, QVector<EventList *> >::iterator j = eventlist.find(id);

    if (j == eventlist.end()) {
        return 0;
    }

    QVector<EventList *> &evec = j.value();

    bool first = true;
    qint64 max = 0, t1;

    int evec_size=evec.size();

    for (int i = 0; i < evec_size; ++i) {
        t1 = evec[i]->last();

        if (first) {
            max = t1;
            first = false;
        } else {
            if (max < t1) { max = t1; }
        }
    }

    m_lastchan[id] = max;

    if (s_machine->type() == MT_CPAP) {
        max += drift;
    }

    return max;
}
bool Session::channelDataExists(ChannelID id)
{
    if (s_events_loaded) {
        QHash<ChannelID, QVector<EventList *> >::iterator j = eventlist.find(id);

        if (j == eventlist.end()) { // eventlist not loaded.
            return false;
        }

        return true;
    } else {
        qDebug() << "Calling channelDataExists without open eventdata!";
    }

    return false;
}
bool Session::channelExists(ChannelID id)
{
    if ( ! enabled()) {
        return false;
    }

    if (s_events_loaded) {
        QHash<ChannelID, QVector<EventList *> >::iterator j = eventlist.find(id);

        if (j == eventlist.end()) { // eventlist not loaded.
            return false;
        }
    } else {
        QHash<ChannelID, EventDataType>::iterator q = m_cnt.find(id);

        if (q == m_cnt.end()) {
            return false;
        }

        if (q.value() == 0) {
            return false;
        }
    }

    return true;
}

EventDataType Session::countInsideSpan(ChannelID span, ChannelID code)
{
    // TODO: Cache me!

    QHash<ChannelID, QVector<EventList *> >::iterator j = eventlist.find(span);

    if (j == eventlist.end()) {
        return 0;
    }
    QVector<EventList *> &evec = j.value();

    qint64 t1,t2;

    int evec_size=evec.size();

    QList<qint64> start;
    QList<qint64> end;

    // Simplify the span flags to start and end times list
    for (int el = 0; el < evec_size; ++el) {
        EventList &ev = *evec[el];

        for (quint32 i=0; i < ev.count(); ++i) {
            end.push_back(t2=ev.time(i));
            start.push_back(t2 - (qint64(ev.data(i)) * 1000L));
        }
    }

    j = eventlist.find(code);

    if (j == eventlist.end()) {
        return 0;
    }
    QVector<EventList *> &evec2 = j.value();
    evec_size=evec2.size();
    int count = 0;

    int spans = start.size();

    for (int el = 0; el < evec_size; ++el) {
        EventList &ev = *evec2[el];

        for (quint32 i=0; i < ev.count(); ++i) {
            t1 = ev.time(i);
            for (int z=0; z < spans; ++z) {
                if ((t1 >= start.at(z)) && (t1 <= end.at(z))) {
                    count++;
                    break;
                }
            }
        }
    }
    return count;
}

EventDataType Session::rangeCount(ChannelID id, qint64 first, qint64 last)
{
    int total = 0, cnt;

    if (id == AllAhiChannels) {
        for (int i = 0; i < ahiChannels.size(); i++)
            total += rangeCount(ahiChannels.at(i), first, last);
        return (EventDataType)total;
    }

    QHash<ChannelID, QVector<EventList *> >::iterator j = eventlist.find(id);

    if (j == eventlist.end()) {
        return 0;
    }

    QVector<EventList *> &evec = j.value();

    qint64 t, start;

    int evec_size=evec.size();

    for (int i = 0; i < evec_size; ++i) {
        EventList &ev = *evec[i];

        if ((ev.last() < first) || (ev.first() > last)) {
            continue;
        }

        if (ev.type() == EVL_Waveform) {
            qint64 et = last;

            if (et > ev.last()) {
                et = ev.last();
            }

            qint64 st = first;

            if (st < ev.first()) {
                st = ev.first();
            }

            t = (et - st) / ev.rate();
            total += t;
        } else {
            cnt = ev.count();
            start = ev.first();
            quint32 *tptr = ev.rawTime();
            quint32 *eptr = tptr + cnt;

            for (; tptr < eptr; tptr++) {
                t = start + *tptr;

                if (t >= first) {
                    if (t <= last) {
                        total++;
                    } else { break; }
                }
            }
        }
    }

    return (EventDataType)total;
}

double Session::rangeSum(ChannelID id, qint64 first, qint64 last)
{
    QHash<ChannelID, QVector<EventList *> >::iterator j = eventlist.find(id);

    if (j == eventlist.end()) {
        return 0;
    }

    QVector<EventList *> &evec = j.value();
    double sum = 0, gain;

    qint64 t, start;
    EventStoreType *dptr, * eptr;
    quint32 *tptr;
    int cnt, idx = 0;

    qint64 rate;

    int evec_size=evec.size();

    for (int i = 0; i < evec_size; i++) {
        EventList &ev = *evec[i];

        if ((ev.last() < first) || (ev.first() > last)) {
            continue;
        }

        start = ev.first();
        dptr = ev.rawData();
        cnt = ev.count();
        eptr = dptr + cnt;
        gain = ev.gain();
        rate = ev.rate();

        if (ev.type() == EVL_Waveform) {
            if (first > ev.first()) {
                // Skip the samples before first
                idx = (first - ev.first()) / rate;
            }

            dptr += idx; //???? foggy.

            t = start;

            for (; dptr < eptr; dptr++) { //int j=idx;j<cnt;j++) {
                if (t <= last) {
                    sum += EventDataType(*dptr) * gain;
                } else { break; }

                t += rate;
            }
        } else {
            tptr = ev.rawTime();

            for (; dptr < eptr; dptr++) {
                t = start + *tptr++;

                if (t >= first) {
                    if (t <= last) {
                        sum += EventDataType(*dptr) * gain;
                    } else { break; }
                }
            }
        }
    }

    return sum;
}
EventDataType Session::rangeMin(ChannelID id, qint64 first, qint64 last)
{
    QHash<ChannelID, QVector<EventList *> >::iterator j = eventlist.find(id);

    if (j == eventlist.end()) {
        return 0;
    }

    QVector<EventList *> &evec = j.value();
    EventDataType gain, v, min = std::numeric_limits<EventDataType>::max();

    qint64 t, start, rate;
    EventStoreType *dptr, * eptr;
    quint32 *tptr;
    int cnt, idx;

    int evec_size=evec.size();

    for (int i = 0; i < evec_size; ++i) {
        EventList &ev = *evec[i];

        if ((ev.last() < first) || (ev.first() > last)) {
            continue;
        }

        dptr = ev.rawData();
        start = ev.first();
        cnt = ev.count();
        eptr = dptr + cnt;
        gain = ev.gain();

        if (ev.type() == EVL_Waveform) {
            rate = ev.rate();
            t = start;
            idx = 0;

            if (first > ev.first()) {
                // Skip the samples before first
                idx = (first - ev.first()) / rate;
            }

            dptr += idx;

            for (; dptr < eptr; dptr++) { //int j=idx;j<cnt;j++) {
                if (t <= last) {
                    v = EventDataType(*dptr) * gain;

                    if (v < min) {
                        min = v;
                    }
                } else { break; }

                t += rate;
            }
        } else {
            tptr = ev.rawTime();

            for (; dptr < eptr; dptr++) { //int j=0;j<cnt;j++) {
                t = start + *tptr++;

                if (t >= first) {
                    if (t <= last) {
                        v = EventDataType(*dptr) * gain;

                        if (v < min) {
                            min = v;
                        }
                    } else { break; }
                }
            }
        }
    }

    return min;
}

EventDataType Session::rangeMax(ChannelID id, qint64 first, qint64 last)
{
    QHash<ChannelID, QVector<EventList *> >::iterator j = eventlist.find(id);

    if (j == eventlist.end()) {
        return 0;
    }

    QVector<EventList *> &evec = j.value();
    EventDataType gain, v, max = std::numeric_limits<EventDataType>::min();

    qint64 t, start, rate;
    EventStoreType *dptr, * eptr;
    quint32 *tptr;
    int cnt, idx;

    int evec_size=evec.size();

    for (int i = 0; i < evec_size; i++) {
        EventList &ev = *evec[i];

        if ((ev.last() < first) || (ev.first() > last)) {
            continue;
        }

        start = ev.first();
        dptr = ev.rawData();
        cnt = ev.count();
        eptr = dptr + cnt;
        gain = ev.gain();

        if (ev.type() == EVL_Waveform) {
            rate = ev.rate();
            t = start;
            idx = 0;

            if (first > ev.first()) {
                // Skip the samples before first
                idx = (first - ev.first()) / rate;
            }

            dptr += idx;

            for (; dptr < eptr; dptr++) { //int j=idx;j<cnt;j++) {
                if (t <= last) {
                    v = EventDataType(*dptr) * gain;

                    if (v > max) { max = v; }
                } else { break; }

                t += rate;
            }
        } else {
            tptr = ev.rawTime();

            for (; dptr < eptr; dptr++) {
                t = start + *tptr++;

                if (t >= first) {
                    if (t <= last) {
                        v = EventDataType(*dptr) * gain;

                        if (v > max) { max = v; }
                    } else { break; }
                }
            }
        }
    }

    return max;
}

EventDataType Session::count(ChannelID id)
{
    int sum = 0;

    if (id == AllAhiChannels) {
        for (int i = 0; i < ahiChannels.size(); i++)
            sum += count(ahiChannels.at(i));
        return sum;
    }

    QHash<ChannelID, EventDataType>::iterator i = m_cnt.find(id);

    if (i != m_cnt.end()) {
        return i.value();
    }

    QHash<ChannelID, QVector<EventList *> >::iterator j = eventlist.find(id);

    if (j == eventlist.end()) {
//        m_cnt[id] = 0;
        return 0;
    }

    QVector<EventList *> &evec = j.value();

    int evec_size=evec.size();
    if (evec_size == 0)
        return 0;

    for (int i = 0; i < evec_size; ++i) {
        sum += evec.at(i)->count();
    }

    m_cnt[id] = sum;
    return sum;
}

double Session::sum(ChannelID id)
{
    QHash<ChannelID, double>::iterator i = m_sum.find(id);

    if (i != m_sum.end()) {
        return i.value();
    }

    QHash<ChannelID, QVector<EventList *> >::iterator j = eventlist.find(id);

    if (j == eventlist.end()) {
        m_sum[id] = 0;
        return 0;
    }

    QVector<EventList *> &evec = j.value();

    double gain, sum = 0;
    EventStoreType *dptr, * eptr;
    int cnt;

    int evec_size=evec.size();

    for (int i = 0; i < evec_size; ++i) {
        EventList &ev = *(evec[i]);
        gain = ev.gain();
        cnt = ev.count();
        dptr = ev.rawData();
        eptr = dptr + cnt;

        for (; dptr < eptr; dptr++) {
            sum += double(*dptr) * gain;
        }
    }

    m_sum[id] = sum;
    return sum;
}

EventDataType Session::avg(ChannelID id)
{
    QHash<ChannelID, EventDataType>::iterator i = m_avg.find(id);

    if (i != m_avg.end()) {
        return i.value();
    }

    QHash<ChannelID, QVector<EventList *> >::iterator j = eventlist.find(id);

    if (j == eventlist.end()) {
        m_avg[id] = 0;
        return 0;
    }

    QVector<EventList *> &evec = j.value();

    double val = 0, gain;
    int cnt = 0;
    EventStoreType *dptr, * eptr;
    int evec_size=evec.size();

    for (int i = 0; i < evec_size; ++i) {
        EventList &ev = *(evec[i]);
        dptr = ev.rawData();
        gain = ev.gain();
        cnt = ev.count();
        eptr = dptr + cnt;

        for (; dptr < eptr; dptr++) {
            val += double(*dptr) * gain;
        }
    }

    if (cnt > 0) { // Shouldn't really happen.. Should aways contain data
        val /= double(cnt);
    }

    m_avg[id] = val;
    return val;
}
EventDataType Session::cph(ChannelID id) // count per hour
{
    QHash<ChannelID, EventDataType>::iterator i = m_cph.find(id);

    if (i != m_cph.end()) {
        return i.value();
    }

    EventDataType val = count(id);
    val /= hours();

    m_cph[id] = val;
    return val;
}
EventDataType Session::sph(ChannelID id) // sum per hour, assuming id is a time field in seconds
{
    QHash<ChannelID, EventDataType>::iterator i = m_sph.find(id);

    if (i != m_sph.end()) {
        return i.value();
    }

    EventDataType val = sum(id) / 3600.0;
    val = 100.0 / hours() * val;
    m_sph[id] = val;
    return val;
}

EventDataType Session::timeAboveThreshold(ChannelID id, EventDataType threshold)
{
    QHash<ChannelID, EventDataType>::iterator th = m_upperThreshold.find(id);
    if (th != m_upperThreshold.end()) {
        if (fabs(th.value()-threshold) < 0.00000001) { // close enough
            th = m_timeAboveTheshold.find(id);
            if (th != m_timeAboveTheshold.end()) {
                return th.value();
            }
        }
    }
    bool loaded = s_events_loaded;

    OpenEvents();
    QHash<ChannelID, QVector<EventList *> >::iterator j = eventlist.find(id);
    if (j == eventlist.end()) {
        if (!loaded) {
            TrashEvents();
        }
        return 0.0f;
    }

    QVector<EventList *> &evec = j.value();
    int evec_size=evec.size();

    qint64 ti, started=0, total=0;
    EventDataType data;
    int elsize;
    for (int i = 0; i < evec_size; ++i) {
        EventList &ev = *(evec[i]);
        elsize = ev.count();

        for (int j=0; j < elsize; ++j) {
            ti=ev.time(j);
            data=ev.data(j);

            if (started == 0) {
                if (data >= threshold) {
                    started=ti;
                }
            } else {
                if (data < threshold) {
                    total += ti-started;
                    started = 0;
                }
            }
        }
    }
    if (started) {
        total += ti-started;
    }
    EventDataType time = double(total) / 60000.0;

    m_timeAboveTheshold[id] = time;
    m_upperThreshold[id] = threshold;
    if (!loaded) this->TrashEvents(); // otherwise leave it open
    return time;
}

EventDataType Session::timeBelowThreshold(ChannelID id, EventDataType threshold)
{
    QHash<ChannelID, EventDataType>::iterator th = m_lowerThreshold.find(id);
    if (th != m_lowerThreshold.end()) {
        if (fabs(th.value()-threshold) < 0.00000001) { // close enough
            th = m_timeBelowTheshold.find(id);
            if (th != m_timeBelowTheshold.end()) {
                return th.value();
            }
        }
    }
    bool loaded = s_events_loaded;

    QHash<ChannelID, QVector<EventList *> >::iterator j = eventlist.find(id);
    if (j == eventlist.end()) {
        return 0.0f;
    }

    QVector<EventList *> &evec = j.value();
    int evec_size=evec.size();

    qint64 ti, started=0, total=0;
    EventDataType data;
    int elsize;
    for (int i = 0; i < evec_size; ++i) {
        EventList &ev = *(evec[i]);
        elsize = ev.count();

        for (int j=0; j < elsize; ++j) {
            ti=ev.time(j);
            data=ev.data(j);

            if (started == 0) {
                if (data <= threshold) {
                    started=ti;
                }
            } else {
                if (data > threshold) {
                    total += ti-started;
                    started = 0;
                }
            }
        }
    }

    if (started) {
        total += ti-started;
    }

    EventDataType time = double(total) / 60000.0;

    m_timeBelowTheshold[id] = time;
    m_lowerThreshold[id] = threshold;
    if (!loaded) this->TrashEvents(); // otherwise leave it open

    return time;
}


bool sortfunction(EventStoreType i, EventStoreType j) { return (i < j); }

EventDataType Session::percentile(ChannelID id, EventDataType percent)
{
    QHash<ChannelID, QVector<EventList *> >::iterator jj = eventlist.find(id);

    if (jj == eventlist.end()) {
        return 0;
    }

    QVector<EventList *> &evec = jj.value();

    if (percent > 1.0) {
        qWarning() << "Session::percentile() called with > 1.0";
        return 0;
    }

    int evec_size = evec.size();

    if (evec_size == 0) {
        return 0;
    }

    QVector<EventStoreType> array;

    EventDataType gain = evec[0]->gain();

    EventStoreType *dptr, * sptr, *eptr;

    int tt = 0, cnt = 0;

    for (int i = 0; i < evec_size; ++i) {
        EventList &ev = *evec[i];
        cnt = ev.count();
        tt += cnt;
    }

    array.resize(tt);

    for (int i = 0; i < evec_size; ++i) {
        EventList &ev = *evec[i];
        sptr = ev.rawData();
        dptr = array.data();

        eptr = sptr + cnt;

        for (; sptr < eptr; sptr++) {
            *dptr++ = * sptr;
        }
    }

    int n = array.size() * percent;

    if (n > array.size() - 1) { n--; }

    nth_element(array.begin(), array.begin() + n, array.end());

    // slack, no averaging.. fixme if this function is ever used..
    return array[n] * gain;
}

EventDataType Session::wavg(ChannelID id)
{
    QHash<EventStoreType, quint32> vtime;
    QHash<ChannelID, EventDataType>::iterator i = m_wavg.find(id);

    if (i != m_wavg.end()) {
        return i.value();
    }

    updateCountSummary(id);

    QHash<ChannelID, QHash<EventStoreType, quint32> >::iterator j2 = m_timesummary.find(id);

    if (j2 == m_timesummary.end()) {
        return 0;
    }

    QHash<EventStoreType, quint32> &timesum = j2.value();

    if (!m_gain.contains(id)) {
        return 0;
    }

    double s0 = 0, s1 = 0, s2;

    EventDataType val, gain = m_gain[id];

    QHash<EventStoreType, quint32>::iterator vi = timesum.begin();
    QHash<EventStoreType, quint32>::iterator ts_end = timesum.end();

    for (; vi != ts_end; vi++) {
        val = vi.key() * gain;
        s2 = vi.value();
        s0 += s2;
        s1 += val * s2;
    }

    if (s0 > 0) {
        val = s1 / s0;
    } else { val = 0; }

    m_wavg[id] = val;
    return val;
}

EventDataType Session::calcMiddle(ChannelID code)
{
    int c = p_profile->general->prefCalcMiddle();

    if (c == 0) {
        return percentile(code, 0.5); // Median
    } else if (c == 1 ) {
        return wavg(code); // Weighted Average
    } else {
        return avg(code); // Average
    }
}

EventDataType Session::calcMax(ChannelID code)
{
    return p_profile->general->prefCalcMax() ? percentile(code, 0.995f) : Max(code);
}

EventDataType Session::calcPercentile(ChannelID code)
{
    double p = p_profile->general->prefCalcPercentile() / 100.0;
    return percentile(code, p);
}


EventList *Session::AddEventList(ChannelID code, EventListType et, EventDataType gain,
                                 EventDataType offset, EventDataType min, EventDataType max, EventDataType rate, bool second_field)
{
    schema::Channel *channel = &schema::channel[code];

    if (!channel) {
        qWarning() << "Channel" << code << "does not exist!";
        //return nullptr;
    }

    EventList *el = new EventList(et, gain, offset, min, max, rate, second_field);

    eventlist[code].push_back(el);
    //s_machine->registerChannel(chan);
    return el;
}
void Session::offsetSession(qint64 offset)
{
    //qDebug() << "Session starts" << QDateTime::fromSecsSinceEpoch(s_first/1000).toString("yyyy-MM-dd HH:mm:ss");
    s_first += offset;
    s_last += offset;
    QHash<ChannelID, quint64>::iterator it;

    QHash<ChannelID, quint64>::iterator end;

    it = m_firstchan.begin();
    end = m_firstchan.end();
    for (; it != end; it++) {
        if (it.value() > 0) {
            it.value() += offset;
        }
    }

    it = m_lastchan.begin();
    end = m_lastchan.end();
    for (; it != end; it++) {
        if (it.value() > 0) {
            it.value() += offset;
        }
    }

    QHash<ChannelID, QVector<EventList *> >::iterator i;
    QHash<ChannelID, QVector<EventList *> >::iterator el_end=eventlist.end();

    int el_s;

    for (i = eventlist.begin(); i != el_end; i++) {
        el_s=i.value().size();
        for (int j = 0; j < el_s; j++) {
            EventList *e = i.value()[j];

            e->setFirst(e->first() + offset);
            e->setLast(e->last() + offset);
        }
    }

    qDebug() << "Session now starts" << QDateTime::fromSecsSinceEpoch(s_first /
             1000).toString("yyyy-MM-dd HH:mm:ss");

}

qint64 Session::first()
{
    qint64 start = s_first;

    if (s_machine->type() == MT_CPAP) {
        start += qint64(p_profile->cpap->clockDrift()) * 1000L;
    }

    return start;
}

qint64 Session::last()
{
    qint64 last = s_last;

    if (s_machine->type() == MT_CPAP) {
        last += qint64(p_profile->cpap->clockDrift()) * 1000L;
    }

    return last;
}
