/*
 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>
#include <SleepLib/calcs.h>

using namespace std;


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

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

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 { // who cares..
        //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;
}

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)summary_version;
    out << (quint16)filetype_summary;
    out << (quint32)s_machine->id();

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

    out << settings;
    out << m_cnt;
    out << m_sum;
    out << m_avg;
    out << m_wavg;
    out << m_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;
    }

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

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


    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)


    if (version<7) {
        QHash<QString,QVariant> v1;
        in >> v1;
        settings.clear();
        ChannelID code;
        for (QHash<QString,QVariant>::iterator i=v1.begin();i!=v1.end();i++) {
            code=schema::channel[i.key()].id();
            settings[code]=i.value();
        }
        QHash<QString,int> zcnt;
        in >> zcnt;
        for (QHash<QString,int>::iterator i=zcnt.begin();i!=zcnt.end();i++) {
            code=schema::channel[i.key()].id();
            m_cnt[code]=i.value();
        }
        QHash<QString,double> zsum;
        in >> zsum;
        for (QHash<QString,double>::iterator i=zsum.begin();i!=zsum.end();i++) {
            code=schema::channel[i.key()].id();
            m_cnt[code]=i.value();
        }
        QHash<QString,EventDataType> ztmp;
        in >> ztmp; // avg
        for (QHash<QString,EventDataType>::iterator i=ztmp.begin();i!=ztmp.end();i++) {
            code=schema::channel[i.key()].id();
            m_avg[code]=i.value();
        }
        ztmp.clear();
        in >> ztmp; // wavg
        for (QHash<QString,EventDataType>::iterator i=ztmp.begin();i!=ztmp.end();i++) {
            code=schema::channel[i.key()].id();
            m_wavg[code]=i.value();
        }
        ztmp.clear();
        in >> ztmp; // 90p
        for (QHash<QString,EventDataType>::iterator i=ztmp.begin();i!=ztmp.end();i++) {
            code=schema::channel[i.key()].id();
            m_90p[code]=i.value();
        }
        ztmp.clear();
        in >> ztmp; // min
        for (QHash<QString,EventDataType>::iterator i=ztmp.begin();i!=ztmp.end();i++) {
            code=schema::channel[i.key()].id();
            m_min[code]=i.value();
        }
        ztmp.clear();
        in >> ztmp; // max
        for (QHash<QString,EventDataType>::iterator i=ztmp.begin();i!=ztmp.end();i++) {
            code=schema::channel[i.key()].id();
            m_max[code]=i.value();
        }
        ztmp.clear();
        in >> ztmp; // cph
        for (QHash<QString,EventDataType>::iterator i=ztmp.begin();i!=ztmp.end();i++) {
            code=schema::channel[i.key()].id();
            m_cph[code]=i.value();
        }
        ztmp.clear();
        in >> ztmp; // sph
        for (QHash<QString,EventDataType>::iterator i=ztmp.begin();i!=ztmp.end();i++) {
            code=schema::channel[i.key()].id();
            m_sph[code]=i.value();
        }
        QHash<QString,quint64> ztim;
        in >> ztim; //firstchan
        for (QHash<QString,quint64>::iterator i=ztim.begin();i!=ztim.end();i++) {
            code=schema::channel[i.key()].id();
            m_firstchan[code]=i.value();
        }
        ztim.clear();
        in >> ztim; // lastchan
        for (QHash<QString,quint64>::iterator i=ztim.begin();i!=ztim.end();i++) {
            code=schema::channel[i.key()].id();
            m_lastchan[code]=i.value();
        }
        //SetChanged(true);
    } else {
        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;
    }

    if (version<summary_version) {
        qDebug() << "Upgrading Summary file to version" << summary_version;
        UpdateSummaries();
        StoreSummary(filename);
    }
    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)events_version;      // 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 << 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();
            out << e.hasSecondField();
            if (e.hasSecondField()) {
                out << e.min2();
                out << e.max2();
            }
        }
    }
    for (i=eventlist.begin(); i!=eventlist.end(); i++) {
        for (int j=0;j<i.value().size();j++) {
            EventList &e=*i.value()[j];

            for (quint32 c=0;c<e.count();c++) {
                out << e.raw(c);
            }
            if (e.hasSecondField()) {
                for (quint32 c=0;c<e.count();c++) {
                    out << e.raw2(c);
                }
            }
            if (e.type()!=EVL_Waveform) {
                for (quint32 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;
    qint16 version;

    in >> t32;      // Magic Number
    if (t32!=magic) {
        qWarning() << "Wrong Magic number in " << filename;
        return false;
    }

    in >> version;      // File Version

    if (version<6) {    // prior to version 6 is too old to deal with
        qDebug() << "Old File Version";
        //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++) {
        if (version<8) {
            QString txt;
            in >> txt;
            code=schema::channel[txt].id();
        } else {
            in >> code;
        }
        mcorder.push_back(code);
        in >> size2;
        sizevec.push_back(size2);
        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;
            bool second_field=false;
            if (version>=7) // version 7 added this field
                in >> second_field;

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

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

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

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

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

void Session::UpdateSummaries()
{
    calcAHIGraph(this);
    calcRespRate(this);
    calcLeaks(this);

    calcSPO2Drop(this);
    calcPulseChange(this);

    ChannelID id;
    QHash<ChannelID,QVector<EventList *> >::iterator c;
    for (c=eventlist.begin();c!=eventlist.end();c++) {
        id=c.key();
        if (schema::channel[id].type()==schema::DATA) {
            //sum(id); // avg calculates this and cnt.
            Min(id);
            Max(id);
            count(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));
    }

}

bool Session::SearchEvent(ChannelID code, qint64 time, qint64 dist)
{
    qint64 t;
    QHash<ChannelID,QVector<EventList *> >::iterator it;
    it=eventlist.find(code);
    if (it!=eventlist.end()) {
        for (int i=0;i<it.value().size();i++)  {
            EventList *el=it.value()[i];
            for (unsigned j=0;j<el->count();j++) {
                t=el->time(j);
                if (qAbs(time-t)<dist)
                    return true;
            }
        }
    }
    return false;
}


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

    QHash<ChannelID,QVector<EventList *> >::iterator j=eventlist.find(id);
    if (j==eventlist.end()) {
        m_min[id]=0;
        return 0;
    }
    QVector<EventList *> & evec=j.value();

    bool first=true;
    EventDataType min=0,t1;
    for (int i=0;i<evec.size();i++) {
        if (evec[i]->count()==0)
            continue;
        t1=evec[i]->Min();
        if (t1==0 && t1==evec[i]->Max()) continue;
        if (first) {
            min=t1;
            first=false;
        } else {
            if (min>t1) min=t1;
        }
    }
    m_min[id]=min;
    return min;
}
EventDataType Session::Max(ChannelID id)
{
    QHash<ChannelID,EventDataType>::iterator i=m_max.find(id);
    if (i!=m_max.end())
        return i.value();

    QHash<ChannelID,QVector<EventList *> >::iterator j=eventlist.find(id);
    if (j==eventlist.end()) {
        m_max[id]=0;
        return 0;
    }
    QVector<EventList *> & evec=j.value();

    bool first=true;
    EventDataType max=0,t1;
    for (int i=0;i<evec.size();i++) {
        if (evec[i]->count()==0) continue;
        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;
        if (q.value()==0) return false;
    }
    return true;
}

int Session::rangeCount(ChannelID id, qint64 first,qint64 last)
{
    QHash<ChannelID,QVector<EventList *> >::iterator j=eventlist.find(id);
    if (j==eventlist.end()) {
        return 0;
    }
    QVector<EventList *> & evec=j.value();
    int sum=0;

    qint64 t;
    for (int i=0;i<evec.size();i++) {
        EventList & ev=*evec[i];
        for (unsigned j=0;j<ev.count();j++) {
            t=ev.time(j);
            if ((t>=first) && (t<=last)) {
                sum++;
            }
        }
    }
    return sum;
}
double Session::rangeSum(ChannelID id, qint64 first,qint64 last)
{
    QHash<ChannelID,QVector<EventList *> >::iterator j=eventlist.find(id);
    if (j==eventlist.end()) {
        return 0;
    }
    QVector<EventList *> & evec=j.value();
    double sum=0;

    qint64 t;
    for (int i=0;i<evec.size();i++) {
        EventList & ev=*evec[i];
        for (unsigned j=0;j<ev.count();j++) {
            t=ev.time(j);
            if ((t>=first) && (t<=last)) {
                sum+=ev.data(j);
            }
        }
    }
    return sum;
}
EventDataType Session::rangeMin(ChannelID id, qint64 first,qint64 last)
{
    QHash<ChannelID,QVector<EventList *> >::iterator j=eventlist.find(id);
    if (j==eventlist.end()) {
        return 0;
    }
    QVector<EventList *> & evec=j.value();
    EventDataType v,min=999999999;

    qint64 t;
    for (int i=0;i<evec.size();i++) {
        EventList & ev=*evec[i];
        for (unsigned j=0;j<ev.count();j++) {
            t=ev.time(j);
            if ((t>=first) && (t<=last)) {
                v=ev.data(j);
                if (v<min) min=v;
            }
        }
    }
    return min;
}
EventDataType Session::rangeMax(ChannelID id, qint64 first,qint64 last)
{
    QHash<ChannelID,QVector<EventList *> >::iterator j=eventlist.find(id);
    if (j==eventlist.end()) {
        return 0;
    }
    QVector<EventList *> & evec=j.value();
    EventDataType v,max=-999999999;

    qint64 t;
    for (int i=0;i<evec.size();i++) {
        EventList & ev=*evec[i];
        for (unsigned j=0;j<ev.count();j++) {
            t=ev.time(j);
            if ((t>=first) && (t<=last)) {
                v=ev.data(j);
                if (v>max) max=v;
            }
        }
    }
    return max;
}

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 (quint32 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 (quint32 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<EventStoreType,quint32> vtime;
    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();

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

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


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

    for (int i=0;i<size;i++) {
        if (!evec[i]->count()) continue;
        /*lastval=evec[i]->raw(0);
        lasttime=evec[i]->time(0);
        for (quint32 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;
        }*/
        time=evec[i]->time(0)/1000L;
        minval=val=evec[i]->raw(0);
        for (quint32 j=1;j<evec[i]->count();j++) {
            lastval=val;
            lasttime=time;
            val=evec[i]->raw(j);
            if (val<minval) minval=val;
            time=evec[i]->time(j)/1000L;
            td=(time-lasttime);
            if (vtime.contains(lastval)) {
                vtime[lastval]+=td;
            } else vtime[lastval]=td;
        }
        if (lasttime>0) {
            td=(last()/1000L)-time;
            if (vtime.contains(val)) {
                vtime[val]+=td;
            } else vtime[val]=td;

        }
    }

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

EventList * Session::AddEventList(ChannelID code, EventListType et,EventDataType gain,EventDataType offset,EventDataType min, EventDataType max,EventDataType rate,bool second_field)
{
    schema::Channel * channel=&schema::channel[code];
    if (!channel) {
        qWarning() << "Channel" << code << "does not exist!";
        //return NULL;
    }
    EventList * el=new EventList(et,gain,offset,min,max,rate,second_field);
    eventlist[code].push_back(el);
    //s_machine->registerChannel(chan);
    return el;
}
void Session::offsetSession(qint64 offset)
{
    //qDebug() << "Session starts" << QDateTime::fromTime_t(s_first/1000).toString("yyyy-MM-dd HH:mm:ss");
    s_first+=offset;
    s_last+=offset;
    QHash<ChannelID,quint64>::iterator it;

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

    QHash<ChannelID,QVector<EventList *> >::iterator i;
    for (i=eventlist.begin();i!=eventlist.end();i++) {
        for (int j=0;j<i.value().size();j++) {
            EventList *e=i.value()[j];

            e->setFirst(e->first()+offset);
            e->setLast(e->last()+offset);
        }
    }
    qDebug() << "Session now starts" << QDateTime::fromTime_t(s_first/1000).toString("yyyy-MM-dd HH:mm:ss");

}
