/*
 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"
#include "SleepLib/profiles.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=11;
const quint16 events_version=10;

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_enabled=-1;

    s_first=s_last=0;
    s_eventfile="";
    s_evchecksum_checked=false;

}
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_min;
    out << m_max;
    out << m_cph;
    out << m_sph;
    out << m_firstchan;
    out << m_lastchan;

    out << m_valuesummary;
    out << m_timesummary;
    out << m_gain;

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


    QHash<ChannelID,EventDataType> cruft;

    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
        // Ignore this
//        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;
        if (version < 11) {
            cruft.clear();
            in >> cruft; // 90%
            if (version >= 10) {
                cruft.clear();
                in >> cruft;// med
                cruft.clear();
                in >> cruft; //p95
            }
        }
        in >> m_min;
        in >> m_max;
        in >> m_cph;
        in >> m_sph;
        in >> m_firstchan;
        in >> m_lastchan;

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


    }

    if (version<summary_version) {

        qDebug() << "Upgrading Summary file to version" << summary_version;
        UpdateSummaries();
        StoreSummary(filename);
    }
    return true;
}

const quint16 compress_method=1;

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

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

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

    quint16 compress=0;

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

    header << (quint16)compress;

    header << (quint16)s_machine->GetType();// Machine Type

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

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

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

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

            // Store the raw event list data in EventStoreType (16bit short)
            EventStoreType *ptr=e.m_data.data();
            out.writeRawData((char *)ptr,e.count() << 1);
//            for (quint32 c=0;c<e.count();c++) {
//                out << *ptr++;//e.raw(c);
//            }
            // Store the second field, only if there
            if (e.hasSecondField()) {
                ptr=e.m_data2.data();
                out.writeRawData((char *)ptr,e.count() << 1);
//                for (quint32 c=0;c<e.count();c++) {
//                    out << *ptr++; //e.raw2(c);
//                }
            }
            // Store the time delta fields for non-waveform EventLists
            if (e.type()!=EVL_Waveform) {
                quint32 * tptr=e.m_time.data();
                out.writeRawData((char *)tptr,e.count() << 2);
//                for (quint32 c=0;c<e.count();c++) {
//                    out << *tptr++; //e.getTime()[c];
//                }
            }
        }
    }

    qint32 datasize=databytes.size();

    // Checksum the _uncompressed_ data
    quint16 chk=qChecksum(databytes.data(),databytes.size());

    header << datasize;
    header << chk;

    QByteArray data;

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

    file.write(headerbytes);
    file.write(data);
    file.close();
    return true;
}
bool Session::LoadEvents(QString filename)
{
    quint32 magicnum,machid,sessid;
    quint16 version,type,crc16,machtype,compmethod;
    quint8 t8;
    qint32 datasize;

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

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

    QByteArray headerbytes=file.read(42);

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

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

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

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

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

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

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

    if (version>=10) {
        if (compmethod>0) {
            databytes=qUncompress(temp);
            if (!s_evchecksum_checked) {
                if (databytes.size()!=datasize) {
                    qDebug() << "File" << filename << "has returned wrong datasize";
                    return false;
                }
                quint16 crc=qChecksum(databytes.data(),databytes.size());
                if (crc!=crc16) {
                    qDebug() << "CRC Doesn't match in" << filename;
                    return false;
                }
                s_evchecksum_checked=true;
            }
        } else {
            databytes=temp;
        }
    } else databytes=temp;

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

    qint16 mcsize;
    in >> mcsize;   // number of 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.resize(evec.m_count);
            EventStoreType *ptr=evec.m_data.data();

            in.readRawData((char *)ptr, evec.m_count << 1);
//            for (quint32 c=0;c<evec.m_count;c++) {
//                in >> t;
//                *ptr++=t;
//            }
            if (evec.hasSecondField()) {
                evec.m_data2.resize(evec.m_count);
                ptr=evec.m_data2.data();

                in.readRawData((char *)ptr,evec.m_count << 1);
//                for (quint32 c=0;c<evec.m_count;c++) {
//                    in >> t;
//                    *ptr++=t;
//                }
            }
            //last=evec.first();
            if (evec.type()!=EVL_Waveform) {
                evec.m_time.resize(evec.m_count);
                quint32 * tptr=evec.m_time.data();
                in.readRawData((char *)tptr,evec.m_count << 2);
//                for (quint32 c=0;c<evec.m_count;c++) {
//                    in >> x;
//                    *tptr++=x;
//                }
            }
        }
    }

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

void Session::updateCountSummary(ChannelID code)
{
    QHash<ChannelID,QVector<EventList *> >::iterator ev=eventlist.find(code);
    if (ev==eventlist.end()) return;

    QHash<EventStoreType, EventStoreType> valsum;
    QHash<EventStoreType, quint32> timesum;
    QHash<EventStoreType, EventStoreType>::iterator vsi;
    QHash<EventStoreType, quint32>::iterator tsi;

    EventDataType raw,lastraw=0;
    qint64 time,lasttime=0;
    qint32 len;
    for (int i=0;i<ev.value().size();i++) {
        EventList & e=*(ev.value()[i]);
        //if (e.type()==EVL_Waveform) continue;

        for (unsigned j=0;j<e.count();j++) {
            raw=e.raw(j);
            vsi=valsum.find(raw);
            if (vsi==valsum.end()) valsum[raw]=1;
            else vsi.value()++;

            time=e.time(j);

            if (lasttime>0) {
                len=(time-lasttime) / 1000;
            } else len=0;

            tsi=timesum.find(lastraw);
            if (tsi==timesum.end()) timesum[raw]=len;
            else tsi.value()+=len;

            lastraw=raw;
            lasttime=time;
        }
    }
    if (valsum.size()==0) return;

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

void Session::UpdateSummaries()
{
    ChannelID id;
    QHash<ChannelID,QVector<EventList *> >::iterator c;
    calcAHIGraph(this);
    calcRespRate(this);
    calcLeaks(this);
    calcSPO2Drop(this);
    calcPulseChange(this);

    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.
            if (c.value().size()>0) {
                EventList * el=c.value()[0];
                EventDataType gain=el->gain();
                m_gain[id]=gain;
            }
            if (!((id==CPAP_FlowRate) || (id==CPAP_MaskPressureHi) || (id==CPAP_RespEvent) || (id==CPAP_MaskPressure)))
                updateCountSummary(id);

            Min(id);
            Max(id);
            count(id);
            last(id);
            first(id);
            if (((id==CPAP_FlowRate) || (id==CPAP_MaskPressureHi) || (id==CPAP_RespEvent) || (id==CPAP_MaskPressure)))
                continue;

            cph(id);
            sph(id);
            avg(id);
            wavg(id);
//            p90(id);
//            p95(id);
//            median(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;
}

bool Session::enabled()
{
    if (s_enabled>=0) {
        return s_enabled;
    }
    if (!settings.contains(SESSION_ENABLED)) {
        settings[SESSION_ENABLED]=s_enabled=true;
        return true;
    }
    s_enabled=settings[SESSION_ENABLED].toBool();
    return s_enabled;
}

void Session::setEnabled(bool b)
{
    settings[SESSION_ENABLED]=s_enabled=b;
    SetChanged(true);
}


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 (!enabled()) return false;
    if (s_events_loaded) {
        QHash<ChannelID,QVector<EventList *> >::iterator j=eventlist.find(id);
        if (j==eventlist.end())  // eventlist not loaded.
            return false;
    } else {
        QHash<ChannelID,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;
}

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

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

    EventDataType val=percentile(id,0.95);
    m_95p[id]=val;
    return val;

}

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

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

    EventDataType val=percentile(id,0.5);
    m_med[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");

}