/*
 SleepLib Session Implementation
 This stuff contains the base calculation smarts
 Copyright (c)2011 Mark Watkins <jedimark@users.sourceforge.net>
 License: GPL
*/

#include "session.h"
#include <cmath>
#include <QDir>
#include <QDebug>
#include <QMessageBox>
#include <QMetaType>
#include <algorithm>

using namespace std;

Session::Session(Machine * m,SessionID session)
{
    if (!session) {
        session=m->CreateSessionID();
    }
    s_machine=m;
    s_session=session;
    s_changed=false;
    s_events_loaded=false;
    _first_session=true;

    s_first=s_last=0;
    s_eventfile="";
}
Session::~Session()
{
    TrashEvents();
}

void Session::TrashEvents()
// Trash this sessions Events and release memory.
{
    QHash<ChannelID,QVector<EventList *> >::iterator i;
    QVector<EventList *>::iterator j;
    for (i=eventlist.begin(); i!=eventlist.end(); i++) {
        for (j=i.value().begin(); j!=i.value().end(); j++) {
            delete *j;
        }
    }
    s_events_loaded=false;
    eventlist.clear();
}

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

    s_events_loaded=eventlist.size() > 0;
    if (s_events_loaded)
        return true;

    if (!s_eventfile.isEmpty()) {
        bool b=LoadEvents(s_eventfile);
        if (!b) {
            qWarning() << "Error Unpacking Events" << s_eventfile;
            return false;
        }
    }


    return s_events_loaded=true;
};

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);

    QString base;
    base.sprintf("%08lx",s_session);
    base=path+"/"+base;
    //qDebug() << "Storing Session: " << base;
    bool a;
    a=StoreSummary(base+".000"); // if actually has events
    //qDebug() << " Summary done";
    if (eventlist.size()>0) {
        s_eventfile=base+".001";
        StoreEvents(s_eventfile);
    } else {
        qDebug() << "Trying to save empty events file";
    }
    //qDebug() << " Events done";
    s_changed=false;
    s_events_loaded=true;

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

    return a;
}

const quint16 filetype_summary=0;
const quint16 filetype_data=1;

bool Session::StoreSummary(QString filename)
{

    QFile file(filename);
    file.open(QIODevice::WriteOnly);

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

    out << (quint32)magic;
    out << (quint16)dbversion;
    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_90p;
    out << m_min;
    out << m_max;
    out << m_cph;
    out << m_sph;
    out << m_firstchan;
    out << m_lastchan;



    // First output the Machine Code and type for each summary record
/*    for (QHash<ChannelID,QVariant>::iterator i=settings.begin(); i!=settings.end(); i++) {
        ChannelID mc=i.key();
        out << (qint16)mc;
        out << i.value();
    } */
    file.close();
    return true;
}

bool Session::LoadSummary(QString filename)
{
    if (filename.isEmpty()) {
        qDebug() << "Empty summary filename";
        return false;
    }

    QFile file(filename);
    if (!file.open(QIODevice::ReadOnly)) {
        qDebug() << "Couldn't open summary file" << filename;
        return false;
    }

    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) {
        qDebug() << "Wrong magic number in " << filename;
        return false;
    }

    in >> t16;      // DB Version
    if (t16!=dbversion) {
        throw OldDBVersion();
        //qWarning() << "Old dbversion "<< t16 << "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;
    }


    qint32 ts32;
    in >> ts32;      // MachineID (dont need this result)
    if (ts32!=s_machine->id()) {
        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)


    in >> settings;
    in >> m_cnt;
    in >> m_sum;
    in >> m_avg;
    in >> m_wavg;
    in >> m_90p;
    in >> m_min;
    in >> m_max;
    in >> m_cph;
    in >> m_sph;
    in >> m_firstchan;
    in >> m_lastchan;

    /*qint16 sumsize;
    in >> sumsize;  // Summary size (number of Machine Code lists)

    for (int i=0; i<sumsize; i++) {
        in >> t16;      // Machine Code
        ChannelID mc=(ChannelID)t16;
        in >> settings[mc];
    } */

    return true;
}

bool Session::StoreEvents(QString filename)
{
    QFile file(filename);
    file.open(QIODevice::WriteOnly);

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

    out << (quint32)magic;          // Magic Number
    out << (quint16)dbversion;      // File Version
    out << (quint16)filetype_data;  // File type 1 == Event
    out << (quint32)s_machine->id();// Machine ID

    out << (quint32)s_session;      // This session's ID
    out << s_first;
    out << s_last;

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


    QHash<ChannelID,QVector<EventList *> >::iterator i;

    for (i=eventlist.begin(); i!=eventlist.end(); i++) {
        out << (qint16)i.key(); // ChannelID
        out << (qint16)i.value().size();
        for (int j=0;j<i.value().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();
        }
    }
    for (i=eventlist.begin(); i!=eventlist.end(); i++) {
        for (int j=0;j<i.value().size();j++) {
            EventList &e=*i.value()[j];

            for (int c=0;c<e.count();c++) {
                out << e.raw(c);
            }
            if (e.type()!=EVL_Waveform) {
                for (int c=0;c<e.count();c++) {
                    out << e.getTime()[c];
                }
            }
        }
    }

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

    QFile file(filename);
    if (!file.open(QIODevice::ReadOnly)) {
        qDebug() << "Couldn't open file" << filename;
        return false;
    }
    QDataStream in(&file);
    in.setVersion(QDataStream::Qt_4_6);
    in.setByteOrder(QDataStream::LittleEndian);

    quint32 t32;
    quint16 t16;
    quint8 t8;
    //qint16 i16;

    in >> t32;      // Magic Number
    if (t32!=magic) {
        qWarning() << "Wrong Magic number in " << filename;
        return false;
    }
    in >> t16;      // File Version
    if (t16!=dbversion) {
        throw OldDBVersion();
        //qWarning() << "Old dbversion "<< t16 << "summary file.. Sorry, you need to purge and reimport";
        return false;
    }

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

    qint32 ts32;
    in >> ts32;      // MachineID
    if (ts32!=s_machine->id()) {
        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;
    in >> s_last;

    qint16 mcsize;
    in >> mcsize;   // number of Machine Code lists

    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++) {
        in >> t16;
        code=(ChannelID)t16;
        mcorder.push_back(code);
        in >> size2;
        sizevec.push_back(size2);
        for (int j=0;j<size2;j++) {
            in >> ts1;
            in >> ts2;
            in >> evcount;
            in >> t8;
            elt=(EventListType)t8;
            in >> rate;
            in >> gain;
            in >> offset;
            in >> mn;
            in >> mx;
            in >> dim;
            EventList *elist=new EventList(code,elt,gain,offset,mn,mx,rate);
            elist->setDimension(dim);

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

    EventStoreType t;
    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.reserve(evec.m_count);
            for (int c=0;c<evec.m_count;c++) {
                in >> t;
                //evec.m_data[c]=t;
                evec.m_data.push_back(t);
            }
            //last=evec.first();
            if (evec.type()!=EVL_Waveform) {
                evec.m_time.reserve(evec.m_count);
                for (int c=0;c<evec.m_count;c++) {
                    in >> x;
                    //last+=x;
                    evec.m_time.push_back(x);
                    //evec.m_time[c]=x;
                }
            }
        }
    }
    return true;
}

void Session::UpdateSummaries()
{
    ChannelID id;
    QHash<ChannelID,QVector<EventList *> >::iterator c;
    for (c=eventlist.begin();c!=eventlist.end();c++) {
        id=c.key();
        if ((channel[id].channeltype()==CT_Event) || (channel[id].channeltype()==CT_Graph)) {
            //sum(id); // avg calculates this and cnt.
            min(id);
            max(id);
            if ((id==CPAP_FlowRate) || (id==CPAP_MaskPressure)) continue;

            cph(id);
            sph(id);
            avg(id);
            wavg(id);
            p90(id);
            last(id);
            first(id);
        }
    }
    if (channelExists(CPAP_Obstructive)) {
        setCph(CPAP_AHI,cph(CPAP_Obstructive)+cph(CPAP_Hypopnea)+cph(CPAP_ClearAirway));
        setSph(CPAP_AHI,sph(CPAP_Obstructive)+sph(CPAP_Hypopnea)+sph(CPAP_ClearAirway));
    }

}

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,t1;
    for (int i=0;i<evec.size();i++) {
        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,t1;
    for (int i=0;i<evec.size();i++) {
        t1=evec[i]->max();
        if (t1==0 && t1==evec[i]->min()) continue;
        if (first) {
            max=t1;
            first=false;
        } else {
            if (max<t1) max=t1;
        }
    }
    m_max[id]=max;
    return max;
}
qint64 Session::first(ChannelID id)
{
    QHash<ChannelID,quint64>::iterator i=m_firstchan.find(id);
    if (i!=m_firstchan.end())
        return i.value();

    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;
    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;
    return min;
}
qint64 Session::last(ChannelID id)
{
    QHash<ChannelID,quint64>::iterator i=m_lastchan.find(id);
    if (i!=m_lastchan.end())
        return i.value();

    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;
    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;
    return max;
}
bool Session::channelExists(ChannelID id)
{
    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,int>::iterator q=m_cnt.find(id);
        if (q==m_cnt.end())
            return false;
    }
    return true;
}

int Session::count(ChannelID id)
{
    QHash<ChannelID,int>::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 sum=0;
    for (int i=0;i<evec.size();i++) {
        sum+=evec[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 sum=0;
    for (int i=0;i<evec.size();i++) {
        for (int j=0;j<evec[i]->count();j++) {
            sum+=evec[i]->data(j);
        }
    }
    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;
    int cnt=0;
    for (int i=0;i<evec.size();i++) {
        for (int j=0;j<evec[i]->count();j++) {
            val+=evec[i]->data(j);
            cnt++;
        }
    }

    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
{
    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::p90(ChannelID id) // 90th Percentile
{
    QHash<ChannelID,EventDataType>::iterator i=m_90p.find(id);
    if (i!=m_90p.end())
        return i.value();

    if (!eventlist.contains(id)) {
        m_90p[id]=0;
        return 0;
    }

    EventDataType val=percentile(id,0.9);
    m_90p[id]=val;
    return val;
}
bool sortfunction (EventStoreType i,EventStoreType j) { return (i<j); }

EventDataType Session::percentile(ChannelID id,EventDataType percent)
{
    //if (channel[id].channeltype()==CT_Graph) return 0;
    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 size=evec.size();
    if (size==0)
        return 0;

    QVector<EventStoreType> array;

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

    int tt=0,cnt;
    for (int i=0;i<size;i++) {
        cnt=evec[i]->count();
        tt+=cnt;
        array.reserve(tt);
        for (int j=0;j<cnt;j++) {
            array.push_back(evec[i]->raw(j));
        }
    }
    std::sort(array.begin(),array.end(),sortfunction);
    double s=array.size();
    if (!s)
        return 0;
    double i=s*percent;
    double t;
    modf(i,&t);
    int j=t;

    //if (j>=size-1) return array[j];

    return EventDataType(array[j])*gain;
}

EventDataType Session::wavg(ChannelID id)
{
    QHash<ChannelID,EventDataType>::iterator i=m_wavg.find(id);
    if (i!=m_wavg.end())
        return i.value();

    QHash<ChannelID,QVector<EventList *> >::iterator jj=eventlist.find(id);
    if (jj==eventlist.end())
        return 0;
    QVector<EventList *> & evec=jj.value();

    qint64 lasttime=0,time,td;
    EventStoreType val,lastval=0;

    QHash<EventStoreType,quint32> vtime;

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

    for (int i=0;i<evec.size();i++) {
        lastval=evec[i]->raw(0);
        lasttime=evec[i]->time(0);
        for (int j=1;j<evec[i]->count();j++) {
            val=evec[i]->raw(j);
            time=evec[i]->time(j);
            td=(time-lasttime);

            if (vtime.contains(lastval)) {
                vtime[lastval]+=td;
            } else vtime[lastval]=td;
            lasttime=time;
            lastval=val;
        }
    }

    qint64 s0=0,s1=0,s2=0; // 32bit may all be thats needed here..
    for (QHash<EventStoreType,quint32>::iterator i=vtime.begin(); i!=vtime.end(); i++) {
       s0=i.value();
       s1+=i.key()*s0;
       s2+=s0;
    }
    double j=double(s1)/double(s2);
    EventDataType v=j*gain;
    if (v>32768*gain) {
        v=0;
    }
    if (v<-(32768*gain)) {
        v=0;
    }
    m_wavg[id]=v;
    return v;
}