/*

SleepLib PRS1 Loader Implementation

Author: Mark Watkins <jedimark64@users.sourceforge.net>
License: GPL
*/

#include <QString>
#include <QDateTime>
#include <QDir>
#include <QFile>
#include <QMessageBox>
#include <QProgressBar>
#include <QDebug>

#include "prs1_loader.h"
#include "SleepLib/session.h"


const int PRS1_MAGIC_NUMBER=2;
const int PRS1_SUMMARY_FILE=1;
const int PRS1_EVENT_FILE=2;
const int PRS1_WAVEFORM_FILE=5;


//********************************************************************************************
/// IMPORTANT!!!
//********************************************************************************************
// Please INCREMENT the prs1_data_version in prs1_loader.h when making changes to this loader
// that change loader behaviour or modify channels.
//********************************************************************************************


extern QProgressBar *qprogress;

map<int,QString> ModelMap;

PRS1::PRS1(Profile *p,MachineID id):CPAP(p,id)
{
    m_class=prs1_class_name;
    properties["Brand"]="Philips Respironics";
    properties["Model"]="System One";

    //SleepFlags= { CPAP_RERA, PRS1_VSnore2, CPAP_FlowLimit, CPAP_Hypopnea, CPAP_Obstructive, CPAP_ClearAirway, CPAP_CSR };
}
PRS1::~PRS1()
{

}


PRS1Loader::PRS1Loader()
{
    m_buffer=new unsigned char [max_load_buffer_size]; //allocate once and reuse.
}

PRS1Loader::~PRS1Loader()
{
    for (map<QString,Machine *>::iterator i=PRS1List.begin(); i!=PRS1List.end(); i++) {
        delete i->second;
    }
    delete [] m_buffer;
}
Machine *PRS1Loader::CreateMachine(QString serial,Profile *profile)
{
    if (!profile)
        return NULL;
    qDebug() << "Create Machine " << serial;

    vector<Machine *> ml=profile->GetMachines(MT_CPAP);
    bool found=false;
    for (vector<Machine *>::iterator i=ml.begin(); i!=ml.end(); i++) {
        if (((*i)->GetClass()=="PRS1") && ((*i)->properties["Serial"]==serial)) {
            PRS1List[serial]=*i; //static_cast<CPAP *>(*i);
            found=true;
            break;
        }
    }
    if (found) return PRS1List[serial];

    //assert(PRS1List.find(serial)==PRS1List.end())
    Machine *m=new PRS1(profile,0);

    PRS1List[serial]=m;
    profile->AddMachine(m);

    m->properties["Serial"]=serial;

    return m;
}
bool isdigit(QChar c)
{
    if ((c>='0') && (c<='9')) return true;
    return false;
}
int PRS1Loader::Open(QString & path,Profile *profile)
{

    QString newpath;

    QString pseries="P-Series";
    if (path.endsWith("/"+pseries)) {
        newpath=path;
    } else {
        newpath=path+"/"+pseries;
    }

    QDir dir(newpath);

    if ((!dir.exists() || !dir.isReadable()))
        return 0;

    //qDebug() << "PRS1Loader::Open newpath=" << newpath;
    dir.setFilter(QDir::NoDotAndDotDot | QDir::Dirs | QDir::Files | QDir::Hidden | QDir::NoSymLinks);
    dir.setSorting(QDir::Name);
    QFileInfoList flist=dir.entryInfoList();


    list<QString> SerialNumbers;
    list<QString>::iterator sn;

    for (int i=0;i<flist.size();i++) {
        QFileInfo fi=flist.at(i);
        QString filename=fi.fileName();

        if ((filename[0]=='P') && (isdigit(filename[1])) && (isdigit(filename[2]))) {
            SerialNumbers.push_back(filename);
        } else if (filename.toLower()=="last.txt") { // last.txt points to the current serial number
            QString file=fi.canonicalFilePath();
            QFile f(file);
            if (!fi.isReadable()) {
                qDebug() << "PRS1Loader: last.txt exists but I couldn't read it!";
                continue;
            }
            if (!f.open(QIODevice::ReadOnly)) {
                qDebug() << "PRS1Loader: last.txt exists but I couldn't open it!";
                continue;
            }
            last=f.readLine(64);
            last=last.trimmed();
            f.close();
        }
    }

    if (SerialNumbers.empty()) return 0;

    Machine *m;
    for (sn=SerialNumbers.begin(); sn!=SerialNumbers.end(); sn++) {
        QString s=*sn;
        m=CreateMachine(s,profile);
        try {
            if (m) OpenMachine(m,newpath+"/"+(*sn),profile);
        } catch(OneTypePerDay e) {
            profile->DelMachine(m);
            PRS1List.erase(s);
            QMessageBox::warning(NULL,"Import Error","This Machine Record cannot be imported in this profile.\nThe Day records overlap with already existing content.",QMessageBox::Ok);
            delete m;
        }
    }
    return PRS1List.size();
}

bool PRS1Loader::ParseProperties(Machine *m,QString filename)
{
    QFile f(filename);
    if (!f.open(QIODevice::ReadOnly))
        return false;

    QString line;
    map<QString,QString> prop;

    QString s=f.readLine();
    QChar sep='=';
    QString key,value;

    while (!f.atEnd()) {
        key=s.section(sep,0,0); //BeforeFirst(sep);
        if (key==s) continue;
        value=s.section(sep,1).trimmed(); //AfterFirst(sep).Strip();
        if (value==s) continue;
        prop[key]=value;
        s=f.readLine();
    }
    bool ok;
    QString pt=prop["ProductType"];
    int i=pt.toInt(&ok,0);
    if (ok) {
        if (ModelMap.find(i)!=ModelMap.end()) {
            m->properties["SubModel"]=ModelMap[i];
        }
    }
    if (prop["SerialNumber"]!=m->properties["Serial"]) {
        qDebug() << "Serial Number in PRS1 properties.txt doesn't match directory structure";
    } else prop.erase("SerialNumber"); // already got it stored.

    for (map<QString,QString>::iterator i=prop.begin(); i!=prop.end(); i++) {
        m->properties[i->first]=i->second;
    }

    f.close();
    return true;
}

int PRS1Loader::OpenMachine(Machine *m,QString path,Profile *profile)
{

    qDebug() << "Opening PRS1 " << path;
    QDir dir(path);
    if (!dir.exists() || (!dir.isReadable()))
         return false;


    dir.setFilter(QDir::NoDotAndDotDot | QDir::Dirs | QDir::Files | QDir::Hidden | QDir::NoSymLinks);
    dir.setSorting(QDir::Name);
    QFileInfoList flist=dir.entryInfoList();

    QString filename;
    if (qprogress) qprogress->setValue(0);

    list<QString> paths;
    for (int i=0;i<flist.size();i++) {
        QFileInfo fi=flist.at(i);
        filename=fi.fileName();

         if ((filename[0].toLower()=='p') && (isdigit(filename[1]))) {
            paths.push_back(fi.canonicalFilePath());
        } else if (filename.toLower()=="properties.txt") {
            ParseProperties(m,fi.canonicalFilePath());
        } else if (filename.toLower()=="e") {
            // don't really give a crap about .004 files yet.
        }
        //if (qprogress) qprogress->Pulse();
    }

    SessionID session;
    long ext;
    typedef vector<QString> StringList;
    map<SessionID,StringList> sessfiles;
    int size=paths.size();
    int cnt=0;
    bool ok;
    for (list<QString>::iterator p=paths.begin(); p!=paths.end(); p++) {
        dir.setPath(*p);
        if (!dir.exists() || !dir.isReadable()) continue;
        flist=dir.entryInfoList();

        for (int i=0;i<flist.size();i++) {
            QFileInfo fi=flist.at(i);
            QString ext_s=fi.fileName().section(".",-1);
            QString session_s=fi.fileName().section(".",0,-2);

            ext=ext_s.toLong(&ok);
            if (!ok) continue;
            session=session_s.toLong(&ok);
            if (!ok) continue;

            if (sessfiles[session].capacity()==0) sessfiles[session].resize(3);

            if (ext==1) {
                 sessfiles[session][0]=fi.canonicalFilePath();
            } else if (ext==2) {
                 sessfiles[session][1]=fi.canonicalFilePath();
            } else if (ext==5) {
                 sessfiles[session][2]=fi.canonicalFilePath();
            }
            cnt++;
            //if (qprogress) qprogress->Pulse(); //Update((float(cnt)/float(size)*25));

            if (qprogress) qprogress->setValue((float(cnt)/float(size)*33.0));
        }
    }
    size=sessfiles.size();
    if (size==0)
        return 0;

    cnt=0;
    for (map<SessionID,StringList>::iterator s=sessfiles.begin(); s!=sessfiles.end(); s++) {
        session=s->first;
        cnt++;
        if (qprogress) qprogress->setValue(33.0+(float(cnt)/float(size)*33.0));

        if (m->SessionExists(session)) continue;
        if (s->second[0].isEmpty()) continue;

        Session *sess=new Session(m,session);
        if (!OpenSummary(sess,s->second[0])) {
            //qWarning() << "PRS1Loader: Dodgy summary file " << s->second[0];

            delete sess;
            continue;
        }
        //sess->SetSessionID(sess->start().GetTicks());
        if (!s->second[1].isEmpty()) {
            if (!OpenEvents(sess,s->second[1])) {
                qWarning() << "PRS1Loader: Couldn't open event file " << s->second[1];
            }
        }
        if (!s->second[2].isEmpty()) {
            if (!OpenWaveforms(sess,s->second[2])) {
                qWarning() << "PRS1Loader: Couldn't open event file " << s->second[2];
            }
        }
        const double ignore_thresh=300.0/3600.0;// Ignore useless sessions under 5 minute
        if (sess->hours()<=ignore_thresh) {
            //qDebug() << "Ignoring short session" << session << "which is only" << (sess->hours()*60.0) << "minute(s) long";
            delete sess;
            continue;
        }
        sess->summary[CPAP_Obstructive]=sess->count_events(CPAP_Obstructive);
        sess->summary[CPAP_Hypopnea]=sess->count_events(CPAP_Hypopnea);
        sess->summary[CPAP_ClearAirway]=sess->count_events(CPAP_ClearAirway);
        sess->summary[CPAP_RERA]=sess->count_events(CPAP_RERA);
        sess->summary[CPAP_FlowLimit]=sess->count_events(CPAP_FlowLimit);
        sess->summary[CPAP_VSnore]=sess->count_events(CPAP_VSnore);

        sess->summary[CPAP_CSR]=sess->sum_event_field(CPAP_CSR,0);
        sess->summary[CPAP_Snore]=sess->sum_event_field(CPAP_Snore,0);


        if (sess->count_events(CPAP_IAP)>0) {
            //sess->summary[CPAP_Mode]!=MODE_ASV)
            sess->summary[CPAP_Mode]=MODE_BIPAP;

            sess->summary[BIPAP_PSAverage]=sess->weighted_avg_event_field(CPAP_PS,0);
            sess->summary[BIPAP_PSMin]=sess->min_event_field(CPAP_PS,0);
            sess->summary[BIPAP_PSMax]=sess->max_event_field(CPAP_PS,0);

            if (sess->summary[CPAP_PressureReliefType].toInt()!=PR_NONE) {
                sess->summary[CPAP_PressureReliefType]=PR_BIFLEX;
            }
            sess->summary[CPAP_PressureMedian]=(sess->avg_event_field(CPAP_EAP,0)+sess->avg_event_field(CPAP_IAP,0))/2.0;
            sess->summary[CPAP_PressureAverage]=(sess->weighted_avg_event_field(CPAP_IAP,0)+sess->weighted_avg_event_field(CPAP_EAP,0))/2.0;
            sess->summary[CPAP_PressureMinAchieved]=sess->min_event_field(CPAP_IAP,0);
            sess->summary[CPAP_PressureMaxAchieved]=sess->max_event_field(CPAP_EAP,0);

            sess->summary[BIPAP_EAPAverage]=sess->weighted_avg_event_field(CPAP_EAP,0);
            sess->summary[BIPAP_EAPMin]=sess->min_event_field(CPAP_EAP,0);
            sess->summary[BIPAP_EAPMax]=sess->max_event_field(CPAP_EAP,0);

            sess->summary[BIPAP_IAPAverage]=sess->weighted_avg_event_field(CPAP_IAP,0);
            sess->summary[BIPAP_IAPMin]=sess->min_event_field(CPAP_IAP,0);
            sess->summary[BIPAP_IAPMax]=sess->max_event_field(CPAP_IAP,0);



        } else {
            sess->summary[CPAP_PressureMedian]=sess->weighted_avg_event_field(CPAP_Pressure,0);
            sess->summary[CPAP_PressureAverage]=sess->weighted_avg_event_field(CPAP_Pressure,0);
            sess->summary[CPAP_PressureMinAchieved]=sess->min_event_field(CPAP_Pressure,0);
            sess->summary[CPAP_PressureMaxAchieved]=sess->max_event_field(CPAP_Pressure,0);
            if (sess->summary.find(CPAP_PressureMin)==sess->summary.end()) {
                sess->summary[CPAP_BrokenSummary]=true;
                //sess->set_last(sess->first());
                if (sess->summary[CPAP_PressureMinAchieved]==sess->summary[CPAP_PressureMaxAchieved]) {
                    sess->summary[CPAP_Mode]=MODE_CPAP;
                } else {
                    sess->summary[CPAP_Mode]=MODE_UNKNOWN;
                }
                sess->summary[CPAP_PressureReliefType]=PR_UNKNOWN;
            }

        }
        if (sess->summary[CPAP_Mode]==MODE_CPAP) {
            sess->summary[CPAP_PressureAverage]=sess->summary[CPAP_PressureMin];
            sess->summary[CPAP_PressureMax]=sess->summary[CPAP_PressureMin];
        }

        sess->summary[CPAP_LeakMinimum]=sess->min_event_field(CPAP_Leak,0);
        sess->summary[CPAP_LeakMaximum]=sess->max_event_field(CPAP_Leak,0); // should be merged..
        sess->summary[CPAP_LeakMedian]=sess->avg_event_field(CPAP_Leak,0);
        sess->summary[CPAP_LeakAverage]=sess->weighted_avg_event_field(CPAP_Leak,0);

        sess->summary[CPAP_SnoreMinimum]=sess->min_event_field(CPAP_Snore,0);
        sess->summary[CPAP_SnoreMaximum]=sess->max_event_field(CPAP_Snore,0);
        sess->summary[CPAP_SnoreMedian]=sess->avg_event_field(CPAP_Snore,0);
        sess->summary[CPAP_SnoreAverage]=sess->weighted_avg_event_field(CPAP_Snore,0);

        //Printf(sess->start().Format()+wxT(" avgsummary=%.3f avgmine=%.3f\n"),sess->summary[CPAP_PressureAverage].GetDouble(),sess->weighted_avg_event_field(CPAP_Pressure,0));
        sess->SetChanged(true);
        m->AddSession(sess,profile);

    }
    QString s;
    s.sprintf("%i",prs1_data_version);
    m->properties["DataVersion"]=s;
    m->Save(); // Save any new sessions to disk in our format */
    if (qprogress) qprogress->setValue(100);
    //qDebug() << "OpenMachine Done";
    return true;
}

bool PRS1Loader::OpenSummary(Session *session,QString filename)
{
    int size,seconds,br,htype,version,sequence;
    qint64 timestamp;
    unsigned char header[24];
    unsigned char ext,sum;

    //qDebug() << "Opening PRS1 Summary " << filename;
    QFile f(filename);

    if (!f.open(QIODevice::ReadOnly))
          return false;

    if (!f.exists())
        return false;

    int hl=16;

    br=f.read((char *)header,hl);

    if (header[0]!=PRS1_MAGIC_NUMBER)
        return false;

    sequence=size=timestamp=seconds=ext=0;
    sequence=(header[10] << 24) | (header[9] << 16) | (header[8] << 8) | header[7];
    timestamp=(header[14] << 24) | (header[13] << 16) | (header[12] << 8) | header[11];
    size=(header[2] << 8) | header[1];
    ext=header[6];
    htype=header[3]; // 00 = normal // 01=waveform // could be a bool?
    version=header[4];
    sequence=sequence;
    version=version; // don't need it here?

    htype=htype; // shut the warning up.. this is useless.

    if (ext!=PRS1_SUMMARY_FILE)
        return false;

    size-=(hl+2);

    // Calculate header checksum and compare to verify header
    sum=0;
    for (int i=0; i<hl-1; i++) sum+=header[i];
    if (sum!=header[hl-1])
        return false;

    if (size<=19) {
      //  qDebug() << "Ignoring short session file " << filename;
        return false;
    }

    qint64 date=timestamp*1000;


    //memset(m_buffer,0,size);
    unsigned char * buffer=m_buffer;
    br=f.read((char *)buffer,size);
    if (br<size) {
        return false;
    }
    if (size<0x30)
        return true;

    session->set_first(date);

    double max;
    session->summary[CPAP_PressureMin]=(double)buffer[0x03]/10.0;
    session->summary[CPAP_PressureMax]=max=(double)buffer[0x04]/10.0;
    int offset=0;
    if (buffer[0x05]!=0) { // This is a time value for ASV stuff
        // non zero adds extra fields..
        offset=4;
    }

    session->summary[CPAP_RampTime]=(int)buffer[offset+0x06]; // Minutes. Convert to seconds/hours here?
    session->summary[CPAP_RampStartingPressure]=(double)buffer[offset+0x07]/10.0;

    if (max>0) { // Ignoring bipap until I see some more data.
        session->summary[CPAP_Mode]=(int)MODE_APAP;
    } else session->summary[CPAP_Mode]=(int)MODE_CPAP;

    // This is incorrect..
    if (buffer[offset+0x08] & 0x80) { // Flex Setting
        if (buffer[offset+0x08] & 0x08) {
            if (max>0) session->summary[CPAP_PressureReliefType]=(int)PR_AFLEX;
            else session->summary[CPAP_PressureReliefType]=(int)PR_CFLEXPLUS;
        } else session->summary[CPAP_PressureReliefType]=(int)PR_CFLEX;
    } else session->summary[CPAP_PressureReliefType]=(int)PR_NONE;

    session->summary[CPAP_PressureReliefSetting]=(int)buffer[offset+0x08] & 3;
    session->summary[CPAP_HumidifierSetting]=(int)buffer[offset+0x09]&0x0f;
    session->summary[CPAP_HumidifierStatus]=(buffer[offset+0x09]&0x80)==0x80;
    session->summary[PRS1_SystemLockStatus]=(buffer[offset+0x0a]&0x80)==0x80;
    session->summary[PRS1_SystemResistanceStatus]=(buffer[offset+0x0a]&0x40)==0x40;
    session->summary[PRS1_SystemResistanceSetting]=(int)buffer[offset+0x0a]&7;
    session->summary[PRS1_HoseDiameter]=(int)((buffer[offset+0x0a]&0x08)?15:22);
    session->summary[PRS1_AutoOff]=(buffer[offset+0x0c]&0x10)==0x10;
    session->summary[PRS1_MaskAlert]=(buffer[offset+0x0c]&0x08)==0x08;
    session->summary[PRS1_ShowAHI]=(buffer[offset+0x0c]&0x04)==0x04;

    unsigned duration=buffer[offset+0x14] | (buffer[0x15] << 8);
    session->summary[CPAP_Duration]=(int)duration;
    //qDebug() << "ID: " << session->session() << " " << duration;
    //float hours=float(duration)/3600.0;
    //session->set_hours(hours);
    if (!duration)
        return false;

    session->set_last(date+qint64(duration)*1000L);

    session->summary[CPAP_PressureMinAchieved]=buffer[offset+0x16]/10.0;
    session->summary[CPAP_PressureMaxAchieved]=buffer[offset+0x17]/10.0;
    session->summary[CPAP_PressureAverage]=buffer[offset+0x18]/10.0;
    session->summary[CPAP_PressurePercentValue]=buffer[offset+0x19]/10.0;
    session->summary[CPAP_PressurePercentName]=90.0;

    if (max==0) {
        session->summary[CPAP_PressureAverage]=session->summary[CPAP_PressureMin];
    }
   /*/if (size==0x4d) {

        session->summary[CPAP_Obstructive]=(int)buffer[offset+0x1C] | (buffer[offset+0x1D] << 8);
        session->summary[CPAP_ClearAirway]=(int)buffer[offset+0x20] | (buffer[offset+0x21] << 8);
        session->summary[CPAP_Hypopnea]=(int)buffer[offset+0x2A] | (buffer[offset+0x2B] << 8);
        session->summary[CPAP_RERA]=(int)buffer[offset+0x2E] | (buffer[offset+0x2F] << 8);
        session->summary[CPAP_FlowLimit]=(int)buffer[offset+0x30] | (buffer[offset+0x31] << 8);
    }*/
    return true;
}

// v2 event parser.
bool PRS1Loader::Parse002(Session *session,unsigned char *buffer,int size,qint64 timestamp)
{
    MachineCode Codes[]={
        PRS1_Unknown00, PRS1_Unknown01, CPAP_Pressure, CPAP_EAP, PRS1_PressurePulse, CPAP_RERA, CPAP_Obstructive, CPAP_ClearAirway,
        PRS1_Unknown08, PRS1_Unknown09, CPAP_Hypopnea, PRS1_Unknown0B, CPAP_FlowLimit, CPAP_VSnore, PRS1_Unknown0E, CPAP_CSR, PRS1_Unknown10,
        CPAP_Leak, PRS1_Unknown12
    };
    int ncodes=sizeof(Codes)/sizeof(MachineCode);

    EventDataType data[10];

    //qint64 start=timestamp;
    qint64 t=timestamp;
    qint64 tt;
    int pos=0;
    int cnt=0;
    short delta;//,duration;
    while (pos<size) {
        unsigned char code=buffer[pos++];
        if (code>=ncodes) {
            qDebug() << "Illegal PRS1 code " << hex << int(code) << " appeared at " << hex << pos+16;
            return false;
        }
        //if (code==0xe) {
        //    pos+=2;
        //} else
        if (code!=0x12) {
            //delta=buffer[pos];
            //duration=buffer[pos+1];
            delta=buffer[pos+1] << 8 | buffer[pos];
            pos+=2;
            //QDateTime d=QDateTime::fromMSecsSinceEpoch(t);
            //qDebug()<< d.toString("yyyy-MM-dd HH:mm:ss") << ": " << hex << pos+15 << " " << hex << int(code) << int(delta);
            t+=delta*1000;
        }
        MachineCode cpapcode=Codes[(int)code];
        tt=t;
        cnt++;
        //int fc=0;
        switch (code) {
        case 0x01: // Unknown            
            session->AddEvent(new Event(t,cpapcode, data,0));
            break;
        case 0x00: // Unknown (ASV Pressure value) // could this be RLE?
            // offset?
            data[0]=buffer[pos++];
            session->AddEvent(new Event(t,cpapcode, data,1));
            break;
        case 0x02: // Pressure
            data[0]=buffer[pos++]/10.0;
            session->AddEvent(new Event(t,cpapcode, data,1));
            break;
        case 0x03: // BIPAP Pressure
            data[0]=buffer[pos++];
            data[1]=buffer[pos++];
            data[0]/=10.0;
            data[1]/=10.0;
            session->AddEvent(new Event(t,CPAP_EAP, data, 1));
            session->AddEvent(new Event(t,CPAP_IAP, &data[1], 1));
            data[1]-=data[0];
            session->AddEvent(new Event(t,CPAP_PS, &data[1], 1));
            break;
        case 0x04: // Pressure Pulse
            data[0]=buffer[pos++];
            session->AddEvent(new Event(t,cpapcode, data,1));
            //qDebug() << hex << data[0];
            break;
        case 0x05: // RERA
        case 0x06: // Obstructive Apoanea
        case 0x07: // Clear Airway
        case 0x0a: // Hypopnea
        case 0x0c: // Flow Limitation
            data[0]=buffer[pos++];
            tt-=data[0]*1000; // Subtract Time Offset
            session->AddEvent(new Event(tt,cpapcode,data,1));
            break;
        case 0x0b: // ASV Codes
            data[0]=buffer[pos++];
            data[1]=buffer[pos++];
            session->AddEvent(new Event(tt,cpapcode,data,2));
            break;
        case 0x0d: // Vibratory Snore
            session->AddEvent(new Event(t,cpapcode, data,0));
            break;
        case 0x11: // Leak Rate
            data[0]=buffer[pos++];
            data[1]=buffer[pos++];
            session->AddEvent(new Event(t,cpapcode, data,1));
            session->AddEvent(new Event(t,CPAP_Snore,&data[1],1));
            if (data[1]>0) {
                session->AddEvent(new Event(t,PRS1_VSnore2, &data[1],1));
            }
            break;
        case 0x0e: // Unknown
            data[0]=((char *)buffer)[pos++];
            data[1]=buffer[pos++]; //(buffer[pos+1] << 8) | buffer[pos];
            //data[0]/=10.0;
            //pos+=2;
            data[2]=buffer[pos++];
            //qDebug() << hex << data[0] << data[1] << data[2];
            session->AddEvent(new Event(t,cpapcode, data, 3));
            //tt-=data[1]*1000;
            //session->AddEvent(new Event(t,CPAP_CSR, data, 2));
            break;
        case 0x10: // Unknown
            data[0]=buffer[pos++]; // << 8) | buffer[pos];
            data[1]=buffer[pos++];
            data[2]=buffer[pos++];
            session->AddEvent(new Event(t,cpapcode, data, 3));
            break;
        case 0x0f: // Cheyne Stokes Respiration
            data[0]=buffer[pos+1]<<8 | buffer[pos];
            pos+=2;
            data[1]=buffer[pos++];
            tt-=data[1]*1000;
            session->AddEvent(new Event(tt,cpapcode, data, 2));
            break;
        case 0x12: // Summary
            data[0]=buffer[pos++];
            data[1]=buffer[pos++];
            data[2]=buffer[pos+1]<<8 | buffer[pos];
            pos+=2;
            session->AddEvent(new Event(t,cpapcode, data,3));
            break;
        default:
            // ERROR!!!
            qWarning() << "Some new fandangled PRS1 code detected " << hex << int(code) << " at " << pos+15;
            return false;
        }
    }
    return true;
}

bool PRS1Loader::Parse002ASV(Session *session,unsigned char *buffer,int size,qint64 timestamp)
{
    MachineCode Codes[]={
        PRS1_Unknown00, PRS1_Unknown01, CPAP_Pressure, CPAP_EAP, PRS1_PressurePulse, CPAP_Obstructive, CPAP_ClearAirway, CPAP_Hypopnea,
        PRS1_Unknown08,  CPAP_FlowLimit, PRS1_Unknown0A, CPAP_CSR, PRS1_Unknown0C, CPAP_VSnore, PRS1_Unknown0E, PRS1_Unknown0F, PRS1_Unknown10,
        CPAP_Leak, PRS1_Unknown12
    };

    int ncodes=sizeof(Codes)/sizeof(MachineCode);

    EventDataType data[10];

    //qint64 start=timestamp;
    qint64 t=timestamp;
    qint64 tt;
    int pos=0;
    int cnt=0;
    short delta;//,duration;
    QDateTime d;
    while (pos<size) {
        unsigned char code=buffer[pos++];
        if (code>=ncodes) {
            qDebug() << "Illegal PRS1 code " << hex << int(code) << " appeared at " << hex << pos+16;
            return false;
        }
        //QDateTime d=QDateTime::fromMSecsSinceEpoch(t);
        //qDebug()<< d.toString("yyyy-MM-dd HH:mm:ss") << ": " << hex << pos+15 << " " << hex << int(code) ;
        if (code==0) {
        } else
        if (code!=0x12) {
            delta=buffer[pos];
            //duration=buffer[pos+1];
            //delta=buffer[pos+1] << 8 | buffer[pos];
            pos+=2;
            t+=delta*1000;
        }
        MachineCode cpapcode=Codes[(int)code];
        //EventDataType PS;
        tt=t;
        cnt++;
        int fc=0;
        switch (code) {
        case 0x01: // Unknown
            session->AddEvent(new Event(t,cpapcode, data,0));
            break;
        case 0x00: // Unknown (ASV Pressure value)
            // offset?
            data[0]=buffer[pos++];
            fc++;
            if (!buffer[pos-1]) {   // WTH???
                data[1]=buffer[pos++];
                fc++;
            }
            if (!buffer[pos-1]) {
                data[2]=buffer[pos++];
                fc++;
            }
            session->AddEvent(new Event(t,cpapcode, data,2));
            break;
        case 0x02: // Pressure
            data[0]=buffer[pos++]/10.0; // crappy EPAP pressure value.
            //session->AddEvent(new Event(t,cpapcode, data,1));
            break;
        case 0x04: // Pressure Pulse
            data[0]=buffer[pos++];
            session->AddEvent(new Event(t,cpapcode, data,1));
            break;

        case 0x0a:
            code=0x0a;
        case 0x05:
        case 0x06:
        case 0x07:
        case 0x0c:
            data[0]=buffer[pos++];
            tt-=data[0]*1000; // Subtract Time Offset
            session->AddEvent(new Event(tt,cpapcode,data,1));
            break;
        case 0x0b: // Cheyne Stokes
            data[0]=((unsigned char *)buffer)[pos+1]<<8 | ((unsigned char *)buffer)[pos];
            data[0]*=2;
            pos+=2;
            data[1]=((unsigned char *)buffer)[pos]; //|buffer[pos+1] << 8
            pos+=1;
            //tt-=delta;
            tt-=data[1]*1000;
            session->AddEvent(new Event(tt,cpapcode, data, 2));
            //tt+=delta;
            break;
        case 0x08: // ASV Codes
        case 0x09: // ASV Codes
            data[0]=buffer[pos];
            tt-=buffer[pos++]*1000; // Subtract Time Offset
            session->AddEvent(new Event(tt,cpapcode,data,1));
            break;
        case 0x0d: // All the other ASV graph stuff.
            d=QDateTime::fromMSecsSinceEpoch(t);
            data[0]=buffer[pos++]/10.0;
            session->AddEvent(new Event(t,CPAP_IAP,&data[0],1)); //correct
            data[1]=buffer[pos++]/10.0; // Low IPAP
            session->AddEvent(new Event(t,CPAP_IAPLO,&data[1],1)); //correct
            data[2]=buffer[pos++]/10.0; // Hi IPAP
            session->AddEvent(new Event(t,CPAP_IAPHI,&data[2],1)); //correct
            data[3]=buffer[pos++];//Leak
            session->AddEvent(new Event(t,CPAP_Leak,&data[3],1)); // correct
            data[4]=buffer[pos++];//Breaths Per Minute
            session->AddEvent(new Event(t,CPAP_RespiratoryRate,&data[4],1)); //correct
            data[5]=buffer[pos++];//Patient Triggered Breaths
            session->AddEvent(new Event(t,CPAP_PatientTriggeredBreaths,&data[5],1)); //correct
            data[6]=buffer[pos++];//Minute Ventilation
            session->AddEvent(new Event(t,CPAP_MinuteVentilation,&data[6],1)); //correct
            data[7]=buffer[pos++]*10.0; // Tidal Volume
            session->AddEvent(new Event(t,CPAP_TidalVolume,&data[7],1)); //correct
            data[8]=buffer[pos++];
            session->AddEvent(new Event(t,CPAP_Snore,&data[8],1)); //correct
            if (data[8]>0) {
                session->AddEvent(new Event(t,CPAP_VSnore,&data[8],1)); //correct
            }
            data[9]=buffer[pos++]/10.0; // EPAP
            session->AddEvent(new Event(t,CPAP_EAP,&data[9],1)); //correct

            data[2]-=data[9]; // Pressure Support
            session->AddEvent(new Event(t,CPAP_PS,&data[2],1)); //correct
            //qDebug()<< d.toString("yyyy-MM-dd HH:mm:ss") << hex << session->session() << pos+15 << hex << int(code) << ": " << hex << int(data[0]) << " " << int(data[1]) << " " << int(data[2])  << " " << int(data[3]) << " " << int(data[4]) << " " << int(data[5])<< " " << int(data[6]) << " " << int(data[7]) << " " << int(data[8]) << " " << int(data[9]);
            break;
        case 0x03: // BIPAP Pressure
            data[0]=buffer[pos++];
            data[1]=buffer[pos++];
            data[0]/=10.0;
            data[1]/=10.0;
            session->AddEvent(new Event(t,CPAP_EAP, data, 1));
            session->AddEvent(new Event(t,CPAP_IAP, &data[1], 1));
            break;
        case 0x11: // Leak Rate
            data[0]=buffer[pos++];
            session->AddEvent(new Event(t,cpapcode, data,1));
            break;
        case 0x0e: // Unknown
            data[0]=buffer[pos++]; // << 8) | buffer[pos];
            session->AddEvent(new Event(t,cpapcode, data, 1));
            break;

        case 0x10: // Unknown
            data[0]=buffer[pos++]; // << 8) | buffer[pos];
            data[1]=buffer[pos++];
            data[2]=buffer[pos++];
            session->AddEvent(new Event(t,cpapcode, data, 3));
            break;
        case 0x0f:
            data[0]=buffer[pos+1]<<8 | buffer[pos];
            pos+=2;
            data[1]=buffer[pos]; //|buffer[pos+1] << 8
            pos+=1;
            tt-=data[1]*1000;
            session->AddEvent(new Event(tt,cpapcode, data, 2));
            break;
        case 0x12: // Summary
            data[0]=buffer[pos++];
            data[1]=buffer[pos++];
            data[2]=buffer[pos+1]<<8 | buffer[pos];
            pos+=2;
            session->AddEvent(new Event(t,cpapcode, data,3));
            break;
        default:
            // ERROR!!!
            qWarning() << "Some new fandangled PRS1 code detected " << hex << int(code) << " at " << pos+15;
            return false;
        }
    }
    return true;
}


bool PRS1Loader::OpenEvents(Session *session,QString filename)
{
    int size,sequence,seconds,br,version;
    qint64 timestamp;
    unsigned char header[24]; // use m_buffer?
    unsigned char ext,htype;

    QFile f(filename);
    if (!f.open(QIODevice::ReadOnly))
        return false;

    int hl=16;

    br=f.read((char *)header,hl);

    if (header[0]!=PRS1_MAGIC_NUMBER)
        return false;

    sequence=size=timestamp=seconds=ext=0;
    sequence=(header[10] << 24) | (header[9] << 16) | (header[8] << 8) | header[7];
    timestamp=(header[14] << 24) | (header[13] << 16) | (header[12] << 8) | header[11];
    size=(header[2] << 8) | header[1];
    ext=header[6];
    htype=header[3]; // 00 = normal // 01=waveform // could be a bool?
    version=header[4];// | header[4];

    htype=htype;
    sequence=sequence;
    if (ext!=PRS1_EVENT_FILE)  // 2 == Event file
        return false;

    //size|=(header[3]<<16) | (header[4]<<24); // the jury is still out on the 32bitness of one. doesn't matter here anyway.

    size-=(hl+2);

    unsigned char sum=0;
    for (int i=0; i<hl-1; i++) sum+=header[i];
    if (sum!=header[hl-1]) return false;

    unsigned char *buffer=(unsigned char *)m_buffer;
    br=f.read((char *)buffer,size);
    if (br<size) {
        return false;
    }
    if (version==0) {
        if (!Parse002(session,buffer,size,timestamp*1000L)) {
            qDebug() << "Couldn't Parse PRS1 Event File " << filename;
            return false;
        }
    } else if (version==5) {
        if (!Parse002ASV(session,buffer,size,timestamp*1000L)) {
            qDebug() << "Couldn't Parse PRS1 (ASV) Event File " << filename;
            return false;
        }
    }

    return true;
}

struct WaveHeaderList {
    quint16 interleave;
    quint8  sample_format;
    WaveHeaderList(quint16 i,quint8 f){ interleave=i; sample_format=f; }
};

bool PRS1Loader::OpenWaveforms(Session *session,QString filename)
{
    //int sequence,seconds,br,htype,version,numsignals;
    QFile file(filename);
    if (!file.open(QIODevice::ReadOnly)) {
        qWarning() << "Couldn't open waveform file" << filename;
        return false;
    }

    int pos,br,size=file.size();
    if (size>max_load_buffer_size) {
        qWarning() << "Waveform too big, increase max_load_buffer_size in PRS1Loader sourcecode" << filename;
        return false;
    }

    br=file.read((char *)m_buffer,size);
    if (br<size) {
        qWarning() << "Couldn't read waveform data.. Disk error?" << filename;
        return false;
    }

    // Look at the initial header and assume this header size for the lot.
    if ((m_buffer[0]!=2) || (m_buffer[6]!=0x05)) {
        qWarning() << "Not correct waveform format" << filename;
        return false;
    }
    quint8 version=m_buffer[4];
    quint32 start=m_buffer[0xb] | m_buffer[0xc] << 8 | m_buffer[0xd] << 16 | m_buffer[0x0e] << 24;
    quint16 num_signals=m_buffer[0x12] | m_buffer[0x13] << 8;
    if (num_signals>2) {
        qWarning() << "More than 2 Waveforms in " << filename;
        return false;
    }
    pos=0x14+(num_signals-1)*3;
    vector<WaveHeaderList> whl;
    // add the in reverse...
    for (int i=0;i<num_signals;i++) {
        quint16 interleave=m_buffer[pos] | m_buffer[pos+1] << 8;
        quint8  sample_format=m_buffer[pos+2];
        whl.push_back(WaveHeaderList(interleave,sample_format));
        pos-=3;
    }
    int hl=15+5+(num_signals*3);
    quint16 duration,length;
    quint32 timestamp,lasttimestamp;


    pos=0;
    int block=0;
    lasttimestamp=start;
    duration=0;
    int corrupt=0;
    char waveform[num_signals][500000];
    int wlength[num_signals];
    int wdur[num_signals];
    for (int i=0;i<num_signals;i++) {
        wlength[i]=0;
        wdur[i]=0;
        //waveform[i].resize(500000);
    }
    SampleFormat *wf[num_signals];
    MachineCode wc[2]={CPAP_FlowRate,CPAP_MaskPressure};
    do {
        timestamp=m_buffer[pos+0xb] | m_buffer[pos+0xc] << 8 | m_buffer[pos+0xd] << 16 | m_buffer[pos+0x0e] << 24;
        register unsigned char sum8=0;
        for (int i=0;i<hl;i++) sum8+=m_buffer[pos+i];
        if (m_buffer[pos+hl]!=sum8) {
            if (block==0) {
                qDebug() << "Faulty Header Checksum, aborting" << filename;
                return false;
            }
            if (++corrupt>3) {
                qDebug() << "3 Faulty Waveform Headers in a row.. Aborting" << filename;
                return false;
            }
            qDebug() << "Faulty Header Checksum, skipping block" << block;
            pos+=length;
            continue;
        } else {
            int diff=timestamp-(lasttimestamp+duration);
            if (block==1) {
                if (diff>0)
                    start-=diff;
            }
            length=m_buffer[pos+0x1] | m_buffer[pos+0x2] << 8;      // block length in bytes
            duration=m_buffer[pos+0xf] | m_buffer[pos+0x10] << 8;    // block duration in seconds
            if (diff<0) {
                qDebug() << "Padding waveform to keep sync" << block;
                //diff=abs(diff);
                for (int i=0;i<num_signals;i++) {
                    for (int j=0;j<diff;j++) {
                        for (int k=0;k<whl[i].interleave;k++)
                            waveform[i][wlength[i]++]=0;
                    }
                    wdur[i]+=diff;
                }

            } else
            if (diff>0 && wlength[0]>0)  {
                qDebug() << "Timestamp restarts" << block << diff << corrupt << duration << timestamp-lasttimestamp << filename;


                for (int i=0;i<num_signals;i++) {
                    wf[i]=new SampleFormat [wlength[i]];
                    for (int j=0;j<wlength[i];j++) {
                        if (whl[i].sample_format)
                            wf[i][j]=((unsigned char*)waveform[i])[j];
                        else
                            wf[i][j]=(waveform[i])[j];
                    }
                    Waveform *w=new Waveform(qint64(start)*1000L,wc[i],wf[i],wlength[i],qint64(wdur[i])*1000L,-128,128); //FlowRate
                    session->AddWaveform(w);
                    wlength[i]=0;
                    wdur[i]=0;
                }
                start=timestamp;
                corrupt=0;
            }
        }

        pos+=hl+1;
        //qDebug() <<  (duration*num_signals*whl[0].interleave) << duration;
        if (num_signals==1) { // no interleave.. this is much quicker.
            int bs=duration*whl[0].interleave;
            memcpy((char *)&(waveform[0])[wlength[0]],(char *)&m_buffer[pos],bs);

            wlength[0]+=bs;

            pos+=bs;
        } else {
            for (int i=0;i<duration;i++) {
                for (int s=0;s<num_signals;s++) {
                    memcpy((char *)&(waveform[s])[wlength[s]],(char *)&m_buffer[pos],whl[s].interleave);
                    wlength[s]+=whl[s].interleave;
                    pos+=whl[s].interleave;
                }
            }
        }
        for (int i=0;i<num_signals;i++) wdur[i]+=duration;
        pos+=2;
        block++;
        lasttimestamp=timestamp;
    } while (pos<size);

    for (int i=0;i<num_signals;i++) {
        wf[i]=new SampleFormat [wlength[i]];
        for (int j=0;j<wlength[i];j++) {
            if (whl[i].sample_format)
                wf[i][j]=((unsigned char*)waveform[i])[j];
            else
                wf[i][j]=(waveform[i])[j];
        }
        Waveform *w=new Waveform(qint64(start)*1000L,wc[i],wf[i],wlength[i],qint64(wdur[i])*1000L,-128,128); //FlowRate
        session->AddWaveform(w);
    }
    return true;
}
/*bool PRS1Loader::OpenWaveforms(Session *session,QString filename)
{
    int size,sequence,seconds,br,htype,version,numsignals;
    unsigned cnt=0;
    qint32 lastts,ts1;
    qint64 timestamp;

    qint64 start=0;
    unsigned char header[20];
    unsigned char ext;

    QFile f(filename);
    if (!f.open(QIODevice::ReadOnly))
        return false;

    const int max_signals=16;
    static qint16 interleave[max_signals]={0};
    static char sampletype[max_signals]={0};

    int hl=0;
    long samples=0;
    qint64 duration=0;
    char * buffer=(char *)m_buffer;
    //bool first2=true;
    long fpos=0;
    //int bsize=0;
    int lasthl=0;
    qint32 expected_timestamp=0;
    while (true) {
        lasthl=hl;
        hl=20;
        br=f.read((char *)header,hl);
        fpos+=hl;
        if (br<hl) {
            if (cnt==0)
                return false;
            break;
        }

        if (header[0]!=PRS1_MAGIC_NUMBER) {
            if (cnt==0)
                return false;
            qWarning() << "Corrupt waveform, trying to recover" << sequence;
            session->summary[CPAP_BrokenWaveform]=true;
            // read the damn bytes anyway..

            br=f.read((char *)header,lasthl-hl+1); // last bit of the header
            if (br<lasthl-hl+1) {
                qWarning() << "End of file, couldn't recover";
                break;
            }
            hl=lasthl;

            br=f.read((char *)&buffer[samples],size);
            if (br<size) {
                //delete [] buffer;
                break;
            }
            samples+=size;
            duration+=seconds;
            br=f.read((char *)header,2);
            fpos+=size+3+lasthl-hl;

            continue;
        }

        //sequence=size=timestamp=seconds=ext=0;
        size=(header[2] << 8) | header[1];
        version=header[4];
        htype=header[3];
        ext=header[6];
        sequence=(header[10] << 24) | (header[9] << 16) | (header[8] << 8) | header[7];
        ts1=timestamp=(header[14] << 24) | (header[13] << 16) | (header[12] << 8) | header[11];
        seconds=(header[16] << 8) | header[15];
        numsignals=header[19] << 8 | header[18];

        sequence=sequence;
        version=version;
        //htype=htype;

        unsigned char sum=0,hchk;
        for (int i=0; i<hl; i++) sum+=header[i];

        if (numsignals>=max_signals) {
            qWarning() << "PRS1Loader::OpenWaveforms() numsignals >= max_signals";
            return false;
        }
        char buf[3];
        for (int i=0;i<numsignals;i++) {
            f.read(buf,3);
            fpos+=3;
            if (br<3) {
                if (cnt==0)
                    return false;
                break;
            }
            sum+=buf[0]+buf[1]+buf[2];
            sampletype[i]=buf[2];
            interleave[i]=buf[1] << 8 | buf[0];
            hl+=3;
        }

        if (f.read((char *)&hchk,1)!=1) { // Read Header byte.
            if (cnt==0)
                return false;
            break;
        }
        fpos+=1;
        if (sum!=hchk)
            return false;

        size-=(hl+3); // 3 == the 8 bit checksum on the end of the header, and the 16bit at the end of the block

        if (!htype) {
            qDebug() << "PRS1 Waveform data has htype set differently";
        }


        if (!start) {
            lastts=timestamp;
         //   expected_timestamp=timestamp+seconds;
            start=timestamp*1000; //convert from epoch to msecs since epoch
            //qDebug() << "Wave: " << cnt << seconds;
        } else {
            qint32 diff=timestamp-expected_timestamp;
            if (version==5) {
                qWarning() << "ASV Waveform out of sync" << sequence << duration << diff << timestamp-lastts;
            } else {
                if (diff<0) {
                    if (duration>abs(diff)) {
                        duration+=diff;  // really Subtracting..
                        samples+=diff*5;
                    } else {
                        qWarning() << "Waveform out of sync beyond the first entry" << sequence << duration << diff;
                    }
                } else if (diff>0) {
                    qDebug() << "Fixing up Waveform sync" << sequence;
                    for (int i=0;i<diff*5;i++) {
                        buffer[samples++]=0;
                    }
                    duration+=diff;
                }
            }
            //qDebug() << "Wave: " << cnt << seconds << diff;
        }


        expected_timestamp=timestamp+seconds*numsignals;

        if (ext!=PRS1_WAVEFORM_FILE) {
            if (cnt==0)
                return false;
            break;
        }

        if (samples+size>=max_load_buffer_size) {
            qWarning("max_load_buffer_size is too small in PRS1 Loader");
            if (cnt==0)
                return false;
            break;
        }
        br=f.read((char *)&buffer[samples],size);
        if (br<size) {
            delete [] buffer;
            break;
        }
        fpos+=size;

        cnt++;

        duration+=seconds;
        samples+=size;

        char chkbuf[2];
        qint16 chksum;
        br=f.read(chkbuf,2);
        fpos+=2;
        if (br<2)
            return false;
        chksum=chkbuf[0] << 8 | chkbuf[1];
        chksum=chksum;
        lastts=ts1;
    }

    if (samples==0)
        return false;

    // Create the buffers for real sample data
    SampleFormat * data[numsignals];
    SampleFormat min[numsignals],max[numsignals];
    bool first[numsignals];
    int pos[numsignals];

    int samplespersignal=samples/numsignals; // TODO: divide by type?
    int ichunksize=0; // interleave chunk size
    for (int i=0;i<numsignals;i++) {
        ichunksize+=interleave[i];
        data[i]=new SampleFormat[samplespersignal];
        min[i]=max[i]=0;
        first[i]=true;
        pos[i]=0;
    }

    // Deinterleave the samples
    SampleFormat c;
    unsigned char * ucbuffer=(unsigned char *)buffer;
    int k=0;
    for (int i=0;i<samples/ichunksize;i++) {
        for (int s=0;s<numsignals;s++) {
            for (int j=0;j<interleave[numsignals-1-s];j++) {
                if (sampletype[numsignals-1-s]==0)
                    c=buffer[k++];
                else if (sampletype[numsignals-1-s]==1) {
                    c=ucbuffer[k++];
                    if (c<40) {
                        c=min[s];
                        //int q=0;
                    }
                }
                if (first[s]) {
                    min[s]=max[s]=c;
                    first[s]=false;
                } else {
                    if (min[s]>c) min[s]=c;
                    if (max[s]<c) max[s]=c;
                }
                data[s][pos[s]++]=c;
            }
        }
    }

    duration*=1000;

    Waveform *w=new Waveform(start,CPAP_FlowRate,data[0],pos[0],duration,min[0],max[0]); //FlowRate
    session->AddWaveform(w);
    if (numsignals>1) {
        Waveform *w=new Waveform(start,CPAP_MaskPressure,data[1],pos[1],duration,min[1],max[1]);
        session->AddWaveform(w);
    }
    return true;
} */

void InitModelMap()
{
    ModelMap[34]="RemStar Pro with C-Flex+";
    ModelMap[35]="RemStar Auto with A-Flex";
    ModelMap[37]="RemStar BiPAP Auto with Bi-Flex";
    ModelMap[0x41]="BiPAP autoSV Advanced";
};


bool initialized=false;
void PRS1Loader::Register()
{
    if (initialized) return;
    qDebug() << "Registering PRS1Loader";
    RegisterLoader(new PRS1Loader());
    InitModelMap();
    initialized=true;
}