/* SleepLib CMS50X Loader Implementation
 *
 * 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. */

//********************************************************************************************
// Please only INCREMENT the cms50f37_data_version in cms50f37_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
//********************************************************************************************

// #include <QProgressBar>
#include <QApplication>
#include <QDir>
#include <QString>
#include <QDataStream>
#include <QDateTime>
#include <QFile>
#include <QDebug>
#include <QList>
#include <QMessageBox>
#include <QLabel>
#include <QVBoxLayout>
#include <QPushButton>

// The qt5.15 obsolescence of hex requires this change.
// this solution to QT's obsolescence is only used in debug statements
#if QT_VERSION >= QT_VERSION_CHECK(5,15,0)
    #define QTHEX     Qt::hex
    #define QTDEC     Qt::dec
#else
    #define QTHEX     hex
    #define QTDEC     dec
#endif

using namespace std;

#include "cms50f37_loader.h"
#include "SleepLib/machine.h"
#include "SleepLib/session.h"

CMS50F37Loader::CMS50F37Loader()
{
    m_type = MT_OXIMETER;
    m_abort = false;
    m_streaming = false;
    m_importing = false;
    started_reading = false;
    cms50dplus = false;

    imp_callbacks = 0;

    m_vendorID = 0x10c4;
    m_productID = 0xea60;

    oxirec = nullptr;

    startTimer.setParent(this);
    resetTimer.setParent(this);

    duration_divisor = 2;

    model = QString();
    vendor = QString();

}

CMS50F37Loader::~CMS50F37Loader()
{
}

bool CMS50F37Loader::openDevice()
{
    if (port.isEmpty()) {
        bool b = scanDevice("",m_vendorID, m_productID);
        if (!b) {
            b = scanDevice("rfcomm", 0, 0); // Linux
        }
        if (!b) {
        	qWarning() << "cms50f37 - No oximeter found";
            return false;
        }
    }
    serial.setPortName(port);
    if (!serial.open(QSerialPort::ReadWrite)) {
    	qDebug() << "cms50f37 - Failed to open oximeter";
        return false;
    }

    // forward this stuff

    // Set up serial port attributes
    serial.setBaudRate(QSerialPort::Baud115200);
    serial.setParity(QSerialPort::NoParity);
    serial.setStopBits(QSerialPort::OneStop);
    serial.setDataBits(QSerialPort::Data8);
    serial.setFlowControl(QSerialPort::NoFlowControl);

    m_streaming = true;
    m_abort = false;
    m_importing = false;

    // connect relevant signals
    connect(&serial,SIGNAL(readyRead()), this, SLOT(dataAvailable()));

    started_import = false;
    started_reading = false;
    finished_import = false;

    resetDevice();
    return true;
}

bool CMS50F37Loader::Detect(const QString &path)
{
    if (p_profile->oxi->oximeterType() == 0) {
        return true;
    }
    Q_UNUSED(path);
    return false;
}

int CMS50F37Loader::Open(const QString & path)
{
    // Only one active Oximeter module at a time, set in preferences

    m_itemCnt = 0;
    m_itemTotal = 0;

    m_abort = false;
    m_importing = false;

    started_import = false;
    started_reading = false;
    finished_import = false;
    setStatus(NEUTRAL);

    imp_callbacks = 0;
    cb_reset = 0;

    m_time.start();

    if (oxirec) {
        trashRecords();
    }

    // Cheating using path for two serial oximetry modes

    if (path.compare("import") == 0) {
        serial.clear();

        sequence = 0;
        buffer.clear();

//        nextCommand();
        setStatus(IMPORTING);

        return 1;
    } else if (path.compare("live") == 0) {
        return 0;
    }
    QString ext = path.section(".", -1);	// find the LAST '.'
    if ((ext.compare("spo2", Qt::CaseInsensitive)==0) || (ext.compare("spo", Qt::CaseInsensitive)==0) || (ext.compare("spor", Qt::CaseInsensitive)==0)) {
        // try to read and process SpoR file..
        return readSpoRFile(path) ? 1 : 0;
    }

    return 0;
}

unsigned char cms50_sequence[] = { 0xa7, 0xa2, 0xa0, 0xb0, 0xac, 0xb3, 0xad, 0xa3, 0xab, 0xa4, 0xa5, 0xaf, 0xa7, 0xa2, 0xa6 };

const int TIMEOUT = 2000;


//const quint8 COMMAND_GET_VERSION = 0xA0;  // not sure of this one
const quint8 COMMAND_CMS50_HELLO2 = 0xA2; // stop live data stream
const quint8 COMMAND_GET_SESSION_COUNT = 0xA3;
const quint8 COMMAND_GET_SESSION_DURATION = 0xA4;
const quint8 COMMAND_GET_SESSION_TIME = 0xA5;
const quint8 COMMAND_GET_SESSION_DATA = 0xA6;
const quint8 COMMAND_CMS50_HELLO1 = 0xA7; // stop stored data stream
const quint8 COMMAND_GET_OXIMETER_MODEL = 0xA8;
const quint8 COMMAND_GET_OXIMETER_VENDOR = 0xA9;
const quint8 COMMAND_GET_OXIMETER_DEVICEID = 0xAA;
const quint8 COMMAND_GET_USER_INFO = 0xAB;
const quint8 COMMAND_GET_USER_COUNT = 0xAD; // for future check and use
const quint8 COMMAND_SESSION_ERASE = 0xAE;
const quint8 COMMAND_GET_OXIMETER_INFO = 0xB0;


int cms50_seqlength = sizeof(cms50_sequence);


QString CMS50F37Loader::getUser()
{
    if (!userName.isEmpty()) return userName;

    sendCommand(COMMAND_GET_USER_INFO);

    QElapsedTimer time;
    time.start();
    do {
        QApplication::processEvents();
    } while (userName.isEmpty() && (time.elapsed() < TIMEOUT));
    
    qDebug() << "cms50f37 - User " << userIdx << " is " << userName;
    return userName;
}

QString CMS50F37Loader::getVendor()
{
    if (!vendor.isEmpty()) return vendor;

    sendCommand(COMMAND_GET_OXIMETER_VENDOR);

    QElapsedTimer time;
    time.start();
    do {
        QApplication::processEvents();
    } while (vendor.isEmpty() && (time.elapsed() < TIMEOUT));
    
    qDebug() << "cms50f37 - Vendor is " << vendor;
    return vendor;
}

QString CMS50F37Loader::getModel()
{
    if (!model.isEmpty()) return model;

    modelsegments = 0;
    sendCommand(COMMAND_GET_OXIMETER_MODEL);

    QElapsedTimer time;
    time.start();
    do {
        QApplication::processEvents();
    } while ((modelsegments < 2) && (time.elapsed() < TIMEOUT));

    // Give a little more time for the second one..
    QThread::msleep(100);
    QApplication::processEvents();

    if (model.startsWith("CMS50I") || model.startsWith("CMS50H")) {
        duration_divisor = 4;
    } else {
        duration_divisor = 2;
    }
    
    qDebug() << "cms50f37 - Model is " << model;
    return model;
}

QString CMS50F37Loader::getDeviceString()
{
     QString VendDev = QString("%1 %2").arg(getVendor()).arg(getModel());
     qDebug() << "cms50f37 - USB Device String is " << VendDev;
     return VendDev;
}

QString CMS50F37Loader::getDeviceID()
{
    if (!devid.isEmpty()) return devid;

    sendCommand(COMMAND_GET_OXIMETER_DEVICEID);

    QElapsedTimer time;
    time.start();
    do {
        QApplication::processEvents();
    } while (devid.isEmpty() && (time.elapsed() < TIMEOUT));
    
    qDebug() << "cms50f37 - Device Id is " << devid;
    return devid;
}

int CMS50F37Loader::getUserCount()  // for future use, check, then add select user if > 1
{
    userCount = -1;
    sendCommand(COMMAND_GET_USER_COUNT);

    QElapsedTimer time;
    time.start();
    do {
        QApplication::processEvents();
    } while ((userCount < 0) && (time.elapsed() < TIMEOUT));

    qDebug() << "cms50f37 - User count is " << userCount;
    return userCount;
}

int CMS50F37Loader::getSessionCount()
{
    session_count = -1;
    sendCommand(COMMAND_GET_SESSION_COUNT);

    QElapsedTimer time;
    time.start();
    do {
        QApplication::processEvents();
    } while ((session_count < 0) && (time.elapsed() < TIMEOUT));

    qDebug() << "cms50f37 - Session count is " << session_count;
    return session_count;
}

int CMS50F37Loader::getOximeterInfo()
{
    device_info = -1;
    sendCommand(COMMAND_GET_OXIMETER_INFO);
    QElapsedTimer time;
    time.start();
    do {
        QApplication::processEvents();
    } while ((device_info < 0) && (time.elapsed() < TIMEOUT));

	qDebug() << "cms50f37 - Device Info is " << device_info;
    return device_info;
}

int CMS50F37Loader::getDuration(int session)
{
    getOximeterInfo();

    duration = -1;
    sendCommand(COMMAND_GET_SESSION_DURATION, session);

    QElapsedTimer time;
    time.start();
    do {
        QApplication::processEvents();
    } while ((duration < 0) && (time.elapsed() < TIMEOUT));

	qDebug() << "cms50f37 - Session duration is " << duration << "Divided by " << duration_divisor;
    return duration / duration_divisor;
}


QDateTime CMS50F37Loader::getDateTime(int session)
{
    QDateTime datetime;
    imp_date = QDate();
    imp_time = QTime();
    sendCommand(COMMAND_GET_SESSION_TIME, session);
    QElapsedTimer time;
    time.start();
    do {
        QApplication::processEvents();
    } while ((imp_date.isNull() || imp_time.isNull()) && (time.elapsed() < TIMEOUT));

    if (imp_date.isNull() && imp_time.isNull())
        datetime = QDateTime(QDate::currentDate(), QTime::currentTime());
    else if ( imp_date.isNull() )
        datetime = QDateTime(QDate::currentDate(), imp_time);
    else if ( imp_time.isNull() )
        datetime = QDateTime(imp_date, QTime::currentTime());
    else
    	datetime = QDateTime(imp_date, imp_time);

    qDebug() << "cms50f37 - Oximeter DateTime is " << datetime.toString("yyyy-MMM-dd HH:mm:ssap");
    return datetime;
}


void CMS50F37Loader::processBytes(QByteArray bytes)
{
    static quint8 resimport = 0;
    int data;

    QString tmpstr;

    int lengths[32] = { 0, 0, 9, 9, 9, 9, 4, 8, 8, 6, 4, 4, 2, 0, 3, 8, 3, 9, 8, 9, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };

    buffer.append(bytes);
    int size = buffer.size();

    int idx = 0;
    int len;

    int year, month, day;

    quint8 pulse;
	quint8 spo2;
	quint16 pi; // perfusion index
	
    do {
        quint8 res = buffer.at(idx);

        len = lengths[res & 0x1f];

        if ((idx+len) > size)
            break;

        if (started_reading && (res != resimport)) {
            len = 0;
        }

        if (len == 0) {
            // lost sync
            if (started_reading) {
                while (idx < size) {
                    res = buffer.at(idx++);
                    if (res == resimport) break;
                }
                // add a dummy to make up for it.
                qDebug() << "cms50f37 - pB: lost sync, padding...";
                oxirec->append(OxiRecord(0,0,0));
            }
            continue;
        }

        // always copy the high bits into the following bytes
        for (int i = 2, msb = buffer.at(idx+1); i < len; i++, msb>>= 1) {
            buffer[idx+i] = (buffer[idx+i] & 0x7f) | (msb & 0x01 ? 0x80 : 0);
        }

        if (!started_reading) switch(res) {
        case 0x02: // Model name string (there are two in sequnce.. second might be the next chunk!)
            data = buffer.at(idx+2);
            if (data == 0) {
                model = QString(buffer.mid(idx+3, 6));
                modelsegments++;
            } else {
                QString extra = QString(buffer.mid(idx+3, 6));
                model += extra.trimmed();
                modelsegments++;
                qDebug() << "cms50f37 - pB: Model:" << model;
            }
            break;
        case 0x03: // Vendor string
            vendor = QString(buffer.mid(idx+2, 7));
            qDebug() << "cms50f37 - pB: Vendor:" << vendor;
            break;

        case 0x04: // Device Identifiers
            devid = QString(buffer.mid(idx+2, 7));
            qDebug() << "cms50f37 - pB: Device ID:" << devid;
            break;

            // COMMAND_GET_USER_INFO
        case 0x05: // 5,80,80,f5,f3,e5,f2,80,80
            userIdx = buffer.at(idx+2);  // for future use
            userName = QString(buffer.mid(idx+3).trimmed());
            qDebug() << "cms50f37 - pB: 0x05:" << userName;
            break;

            // Command GET_VERSION
        case 0x6: // 6,80,80,87
            protocolVersion = buffer.at(idx+3) | (buffer.at(idx+2)<<8);
            break;

            // COMMAND_GET_SESSION_TIME --- the date part
        case 0x07: // 7,80,80,80,94,8e,88,92

            year = QString::asprintf("%02i%02i",buffer.at(idx+4), buffer.at(idx+5)).toInt();
            month = QString::asprintf("%02i", buffer.at(idx+6)).toInt();
            day = QString::asprintf("%02i", buffer.at(idx+7)).toInt();

            if ( year == 0 ) {
                imp_date = QDate::currentDate();
                cms50dplus = true;
            }
            else
                imp_date = QDate(year,month,day);
            qDebug() << "cms50f37 - cms50D+ detected: " << (cms50dplus ? "yes" : "no"); 
            qDebug() << "cms50f37 - pB: ymd " << year << month << day << " impDate: " << imp_date;
            break;

            // COMMAND_GET_SESSION_DURATION
        case 0x08: // 8,80,80,80,a4,81,80,80  // 00, 00, 24, 01, 00, 00

            duration = buffer.at(idx+4);
            duration |= (buffer.at(idx+5) << 8);
            duration |= (buffer.at(idx+6) << 16);
            duration |= (buffer.at(idx+7) << 24);
            break;

            // COMMAND_GET_SESSION_COUNT
        case 0x0a: // a,80,80,81
            userIdx = buffer.at(idx+2);
            session_count = buffer.at(idx+3);
            break;

        case 0x0b:
            timectr++;
            break;

            // COMMAND_CMS50_HELLO1 && COMMAND_CMS50_HELLO2
        case 0xc: // a7 & a2  // responds with: c,80
            //data = buffer.at(idx+1);
            break;

            // Query Perfusion Index available (0 is yes, 1 is no)
        case 0x0e: // e,80,81
            //data = buffer.at(idx+2);
            break;

            // Get User Count
        case 0x10: // 10,80,81
            userCount = buffer.at(idx+2);
            break;

            // COMMAND_GET_OXIMETER_INFO
        case 0x11: // 11,80,81,81,80,80,80,80,80
            device_info = buffer.at(idx+3);
            break;

            // COMMAND_GET_SESSION_TIME
        case 0x12: // 12,80,80,80,82,a6,92,80
            tmpstr = QString::asprintf("%02i:%02i:%02i",buffer.at(idx+4), buffer.at(idx+5), buffer.at(idx+6));
            imp_time = QTime::fromString(tmpstr, "HH:mm:ss");
            qDebug() << "cms50f37 - pB: tmpStr:" << tmpstr << " impTime: " << imp_time;

            break;
        case 0x13: // 13,80,a0,a0,a0,a0,a0,a0,a0
            break;
        case 0x14: //14,80,80,80,80,80,80,80,80
            break;

        case 0x09: // cms50i data sequence
        case 0x0f: // f,80,de,c2,de,c2,de,c2  cms50F data...
            if (!started_import) {
            	qDebug() << "cms50f37 - pB: Starting import";
                started_import = true;
                started_reading = true;
                finished_import = false;

                m_importing = true;

                m_itemCnt=0;
                m_itemTotal=duration;

                have_perfindex = (res == 0x9);

                oxirec = new QVector<OxiRecord>;
                oxirec->reserve(30000);

                oxisessions[m_startTime] = oxirec;

                cb_reset = 1;

                resetTimer.singleShot(2000,this,SLOT(resetImportTimeout()));
                resimport = res;
            }

            break;
        default:
            qDebug() << "cms50f37 - pB: unknown cms50F result?" << QTHEX << (int)res;
            break;
        }

        if (res == 0x09) {
            // 9,80,e1,c4,ce,82  // cms50i data

            pi = buffer.at(idx+4) | (buffer.at(idx+5) << 8);
            pulse = buffer.at(idx+3);
            spo2 = buffer.at(idx+2);
//            qDebug() << "cms50f37 - pB: Pulse=" << pulse << "SPO2=" << spo2 << "PI=" << pi;

            oxirec->append(((spo2 == 0) || (pulse == 0)) ? OxiRecord(0,0,0) : OxiRecord(pulse, spo2, pi));
        } else if (res == 0x0f) {
            // f,80,de,c2,de,c2,de,c2  cms50F data...

            pulse = buffer.at(idx+3);
            spo2 = buffer.at(idx+2);
//            qDebug() << "cms50f37 - pB: Pulse=" << pulse << "SPO2=" << spo2;
            oxirec->append((pulse == 0xff) ? OxiRecord(0,0) : OxiRecord(pulse, spo2));

            pulse = buffer.at(idx+5);
            spo2 = buffer.at(idx+4);
//            qDebug() << "cms50f37 - pB: Pulse=" << pulse << "SPO2=" << spo2;
            oxirec->append((pulse == 0xff) ? OxiRecord(0,0) : OxiRecord(pulse, spo2));

            pulse = buffer.at(idx+7);
            spo2 = buffer.at(idx+6);
//            qDebug() << "cms50f37 - pB: Pulse=" << pulse << "SPO2=" << spo2;
            oxirec->append((pulse == 0xff) ? OxiRecord(0,0) : OxiRecord(pulse, spo2));
        }

        QStringList str;
        for (int i=0; i < len; ++i) {
            str.append(QString::number((unsigned char)buffer.at(idx + i),16));
        }

        if (!started_import) {
          //  startTimer.singleShot(2000, this, SLOT(requestData()));
            importCount = 0;
            qDebug() << "cms50f37 - pB: Read:" << len << size << str.join(",");
        } else {
            importCount++;
//            qDebug() << "cms50f37 - pB: Import:" << len << size << str.join(",");
        }

        idx += len;
    } while (idx < size);


    if (!started_import) {
        imp_callbacks = 0;
    } else {
        emit updateProgress(oxirec->size(), duration);

        imp_callbacks++;
    }

    buffer = buffer.mid(idx);
}


//int CMS50F37Loader::doLiveMode()
//{
//    if (oxirec == nullptr) { // warn
//}
//
//    int available = buffer.size();
//    int idx = 0;
//
//    QByteArray plethy;
//    while (idx < available-5) {
//        if (((unsigned char)buffer.at(idx) & 0x80) != 0x80) {
//            idx++;
//            continue;
//        }
//        int pwave=(unsigned char)buffer.at(idx + 1);
//        int pbeat=(unsigned char)buffer.at(idx + 2);
//        int pulse=((unsigned char)buffer.at(idx + 3) & 0x7f) | ((pbeat & 0x40) << 1);
//        int spo2=(unsigned char)buffer.at(idx + 4) & 0x7f;
//
//        oxirec->append(OxiRecord(pulse, spo2));
//        plethy.append(pwave);
//
//        idx += 5;
//    }
//    emit updatePlethy(plethy);
//
//    return idx;
//}


void CMS50F37Loader::sendCommand(quint8 c)
{
    quint8 cmd[] = { 0x7d, 0x81, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80 };
    cmd[2] |= (c & 0x7f);

    QString out;
    for (int i=0;i < 9;i++) 
       out += QString::asprintf("%02X ",cmd[i]);
    qDebug() << "cms50f37 - Write:" << out;

    if (serial.write((char *)cmd, 9) == -1) {
        qDebug() << "cms50f37 - Couldn't write data bytes to CMS50F";
    }
}

void CMS50F37Loader::sendCommand(quint8 c, quint8 c2)
{
    quint8 cmd[] = { 0x7d, 0x81, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80 };
    cmd[2] |= (c & 0x7f);
    cmd[4] |= (c2 & 0x7f);

    QString out;
    for (int i=0; i < 9; ++i) 
       out += QString::asprintf("%02X ",cmd[i]);
    qDebug() << "cms50f37 - Write:" << out;

    if (serial.write((char *)cmd, 9) == -1) {
        qDebug() << "cms50f37 - Couldn't write data bytes to CMS50F";
    }
}

void CMS50F37Loader::eraseSession(int user, int session)
{
    quint8 cmd[] = { 0x7d, 0x81, COMMAND_SESSION_ERASE, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80 };

    cmd[3] = (user & 0x7f) | 0x80;
    cmd[4] = (session & 0x7f) | 0x80;

    QString out;
    for (int i=0; i < 9; ++i) 
       out += QString::asprintf("%02X ",cmd[i]);
    qDebug() << "cms50f37 - Erase Session: Write:" << out;

    if (serial.write((char *)cmd, 9) == -1) {
        qDebug() << "cms50f37 - Couldn't write Erase session bytes to CMS50F";
    }

    int z = timectr;
    QElapsedTimer time;
    time.start();
    do {
        QApplication::processEvents();
    } while ((timectr == z) && (time.elapsed() < TIMEOUT));
}


void CMS50F37Loader::setDeviceID(const QString & newid)
{
    QString str = newid;
    str.truncate(7);
    if (str.length() < 7) {
        str = QString(" ").repeated(7-str.length()) + str;
    }
    quint8 cmd[] = { 0x04, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80 };

    quint8 msb = 0;

    QByteArray ba = str.toLocal8Bit();
    for (int i=6; i >= 0; i--) {
        msb <<= 1;
        msb |= (ba.at(i) >> 7) & 1;
        cmd[i+2] = ba.at(i) | 0x80;
    }

    cmd[1] = msb | 0x80;

    QString out;
    for (int i=0; i < 9; ++i) 
       out += QString::asprintf("%02X ",cmd[i]);
    qDebug() << "cms50f37 - setDeviceID: Write:" << out;

    if (serial.write((char *)cmd, 9) == -1) {
        qDebug() << "cms50f37 - Couldn't write DeviceID data bytes to CMS50F";
    }

    // Supposed to return 0x04 command, so reset devid..
    devid = QString();

    QElapsedTimer time;
    time.start();
    do {
        QApplication::processEvents();
    } while (devid.isEmpty() && (time.elapsed() < TIMEOUT));
}

void CMS50F37Loader::syncClock()
{
    QDate date = QDate::currentDate();
    int year = date.year();
    quint8 yh = (year / 100) | 0x80;
    quint8 yl = (year % 100) | 0x80;
    quint8 mon = date.month() | 0x80;
    quint8 day = date.day() | 0x80;
    quint8 wd = (date.dayOfWeek() % 7) | 0x80;

    quint8 datecmd[] = { 0x7d, 0x81, 0xb2, yh, yl, mon, day, wd, 0x80 };

    timectr = 0;
    if (serial.write((char *)datecmd, 9) == -1) {
        qDebug() << "cms50f37 - Couldn't write date bytes to CMS50F";
    }

    QElapsedTimer time;
    time.start();
    do {
        QApplication::processEvents();
    } while ((timectr == 0) && (time.elapsed() < TIMEOUT));


    QThread::msleep(100);
    QApplication::processEvents();

    QTime ctime = QTime::currentTime();
    quint8 h = ctime.hour() | 0x80;
    quint8 m = ctime.minute() | 0x80;
    quint8 s = ctime.second() | 0x80;

    quint8 timecmd[] = { 0x7d, 0x81, 0xb1, h, m, s, 0x80, 0x80, 0x80 };

    timectr = 0;
    if (serial.write((char *)timecmd, 9) == -1) {
        qDebug() << "cms50f37 - Couldn't write time bytes to CMS50F";
    }

    time.start();
    do {
        QApplication::processEvents();
    } while ((timectr == 0) && (time.elapsed() < TIMEOUT));
}


void CMS50F37Loader::nextCommand()
{
	qDebug() << "nextCommand sequence: " << sequence;
    if (++sequence < cms50_seqlength) {
        // Send the next command packet in sequence
        sendCommand(cms50_sequence[sequence]);
    } else {
        qDebug() << "cms50f37 - Run out of startup tasks to do and import failed!";
    }
}

void CMS50F37Loader::getSessionData(int session)
{
    resetDevice();
    selected_session = session;
    requestData();
}

void CMS50F37Loader::resetDevice()
{
	qDebug() << "cms50f37 - Resetting oximeter";
    sendCommand(COMMAND_CMS50_HELLO1);
    QThread::msleep(100);
    QApplication::processEvents();
    sendCommand(COMMAND_CMS50_HELLO2);
    QThread::msleep(100);
    QApplication::processEvents();
}

void CMS50F37Loader::requestData()
{
	qDebug() << "cms50f37 - Requesting session data";
    sendCommand(COMMAND_GET_SESSION_DATA, selected_session);
}

void CMS50F37Loader::killTimers()
{
    if (resetTimer.isActive()) resetTimer.stop();
    if (startTimer.isActive()) startTimer.stop();
}

void CMS50F37Loader::startImportTimeout()
{
}

void CMS50F37Loader::resetImportTimeout()
{
    if (finished_import) {
        return;
    }

    if (imp_callbacks != cb_reset) {
        // Still receiving data.. reset timer
        qDebug() << "cms50f37 - Still receiving data in resetImportTimeout()" << imp_callbacks << cb_reset;
        if (resetTimer.isActive())
            resetTimer.stop();

        if (!finished_import)
            resetTimer.singleShot(2000, this, SLOT(resetImportTimeout()));
    } else {
        qDebug() << "cms50f37 - Oximeter device stopped transmitting.. Transfer complete";
        qDebug() << "cms50f37 - Import packet count: " << importCount;
        // We were importing, but now are done
        if (!finished_import && (started_import && started_reading)) {
            qDebug() << "cms50f37 - Switching CMS50F37 back to live mode and finalizing import";
            // Turn back on live streaming so the end of capture can be dealt with


            killTimers();

            finished_import = true;
            m_streaming = false;
            m_importing = false;
            started_reading = false;
            started_import = false;

            emit importComplete(this);

            m_status = NEUTRAL;

            shutdownPorts();

            return;
        }
        qDebug() << "cms50f37 - Should CMS50F37 resetImportTimeout reach here?";
        // else what???
    }
    cb_reset = imp_callbacks;
}

void CMS50F37Loader::shutdownPorts()
{
    closeDevice();
}




bool CMS50F37Loader::readSpoRFile(const QString & path)
{
    QFile file(path);
    if (!file.exists()) {
    	qWarning() << "cms50f37 - Can't find the oximeter file: " << path;
        QMessageBox::warning(nullptr, STR_MessageBox_Error, "<h2>"+tr("Could not find the oximeter file:")+"<br/><br/>"+path+"</h2>");
        return false;
    }

    if (!file.open(QFile::ReadOnly)) {
    	qWarning() << "cms50f37 - Can't open the oximeter file: " << path;
        QMessageBox::warning(nullptr, STR_MessageBox_Error, "<h2>"+tr("Could not open the oximeter file:")+"<br/><br/>"+path+"</h2>");
        return false;
    }

    bool spo2header = false;
    QString ext = path.section('.', -1);
    qDebug() << "cms50f37 - Oximeter file extention is " << ext;
    if (ext.compare("spo2",Qt::CaseInsensitive) == 0) {
        spo2header = true;
        qDebug() << "cms50f37 - Oximeter file looks like an SpO2 type" ;
    }

    QByteArray data;

    qint64 filesize = file.size();
    data = file.readAll();
    QDataStream in(data);
    in.setByteOrder(QDataStream::LittleEndian);
    quint16 pos;
    in >> pos;

    in.skipRawData(pos - 2);

    //long size = data.size();
    int bytes_per_record = 2;

    if (!spo2header) {
        // next is 0x0002
        // followed by 16bit duration in seconds

        // Read date and time (it's a 16bit charset)

        char dchr[20];
        int j = 0;
        for (int i = 0; i < 18 * 2; i += 2) {
            dchr[j++] = data.at(8 + i);
        }

        dchr[j] = 0;
        if (dchr[0]) {
            QString dstr(dchr);
            // Ensure date is correct first to ensure DST is handled correctly
            QDate date = QDate::fromString(dstr.left(8),"MM/dd/yy");
            QTime time = QTime::fromString(dstr.right(8),"HH:mm:ss");
            if (date.year() < 2000) {
                date = date.addYears(100);
            }
            m_startTime = QDateTime(date, time);
        } else {  // this should probaly find the most recent SH data day
            m_startTime = QDateTime(QDate::currentDate(), QTime(0,0,0));  // make it today at midnight
            cms50dplus = true;
        }
    } else { // it is an spo2header

        quint32 samples = 0; // number of samples

        quint32 year, month, day;
        quint32 hour, minute, second;

        if (data.at(pos) != 1) {
            qWarning() << "cms50f37 - .spo2 file" << path << "might be a different";
        }

        // Unknown cruft header...
        in.skipRawData(200);

        in >> year >> month >> day;
        in >> hour >> minute >> second;

        if ( year+month+day == 0 ) {
            m_startTime = QDateTime(QDate::currentDate(), QTime(0,0,0));
            cms50dplus = true;
        }
        else
            m_startTime = QDateTime(QDate(year, month, day), QTime(hour, minute, second));

        // ignoring it for now
        pos += 0x1c + 200;

        in >> samples;

        int remainder = filesize - pos;

        bytes_per_record = remainder / samples;
        qDebug() << "cms50f37 - " << samples << "samples of" << bytes_per_record << "bytes each";

        // CMS50I .spo2 data have 4 digits, a 16bit, followed by spo2 then pulse

    }

    oxirec = new QVector<OxiRecord>;
    oxisessions[m_startTime] = oxirec;

    unsigned char o2, pr;
    quint16 un;

    // Read all Pulse and SPO2 data
    do {
        if (bytes_per_record > 2) {
            in >> un;
        }
        in >> o2;
        in >> pr;

        if ((o2 == 0x7f) && (pr == 0xff)) {
            o2 = pr = 0;
            un = 0;
        }

        if (spo2header) {
            oxirec->append(OxiRecord(pr, o2));
        } else {
            oxirec->append(OxiRecord(o2, pr));
        }
    } while (!in.atEnd());


//    for (int i = pos; i < size - 2;) {
//        o2 = (unsigned char)(data.at(i + 1));
//        pr = (unsigned char)(data.at(i + 0));
//        oxirec->append(OxiRecord(pr, o2));
//        i += 2;
//    }

    // processing gets done later
    return true;
}

void CMS50F37Loader::process()
{
    // Just clean up any extra crap before oximeterimport parses the oxirecords..
    return;
//    if (!oxirec)
//        return;
//    int size=oxirec->size();
//    if (size<10)
//        return;

}



static bool cms50f37_initialized = false;

void CMS50F37Loader::Register()
{
    if (cms50f37_initialized) { return; }

    qDebug() << "Registering CMS50F37Loader";
    RegisterLoader(new CMS50F37Loader());
    cms50f37_initialized = true;
}

