#include "UtsusemiRedisPubSubEventReader.hh"
//////////////////////////////////////////////////////////
UtsusemiRedisPubSubEventReader::
UtsusemiRedisPubSubEventReader(){
    Initialize();
}
//////////////////////////////////////////////////////////
UtsusemiRedisPubSubEventReader::
UtsusemiRedisPubSubEventReader( const UtsusemiRedisPubSubEventReader& obj ){
    Initialize();

    _KeyOfEdb = obj._KeyOfEdb;
    _KeyOfT0b = obj._KeyOfT0b;

    LeftPulseIdTable.resize( obj.LeftPulseIdTable.size() );
    LeftPulseIdTable.assign( obj.LeftPulseIdTable.begin(), obj.LeftPulseIdTable.end() );
    LeftT0IndexTable.resize( obj.LeftT0IndexTable.size() );
    LeftT0IndexTable.assign( obj.LeftT0IndexTable.begin(), obj.LeftT0IndexTable.end() );
    LeftDiffClockTable.resize( obj.LeftDiffClockTable.size() );
    LeftDiffClockTable.assign( obj.LeftDiffClockTable.begin(), obj.LeftDiffClockTable.end() );
    LeftEdbEvents.resize( obj.LeftEdbEvents.size() );
    LeftEdbEvents.assign( obj.LeftEdbEvents.begin(), obj.LeftEdbEvents.end() );

}
//////////////////////////////////////////////////////////
UtsusemiRedisPubSubEventReader::
~UtsusemiRedisPubSubEventReader(){
    if (_RedisPubSubEdb!=NULL){
        if (!Unsubscribe()) UtsusemiError( _MessageTag+"Unsubscribe is failed" );
        delete _RedisPubSubEdb;
        delete _RedisPubSubT0b;
    }
}
//////////////////////////////////////////////////////////
const UInt4 UtsusemiRedisPubSubEventReader::REDIS_PUBSUB_BUF_SIZE = 10000000;
const UInt4 UtsusemiRedisPubSubEventReader::REDIS_PUBSUB_TIMEOUT = 200;
//////////////////////////////////////////////////////////
void UtsusemiRedisPubSubEventReader::
Initialize(){
    _KeyOfEdb = "";
    _KeyOfT0b = "";
    _RedisPubSubEdb = NULL;
    _RedisPubSubT0b = NULL;

    LeftPulseIdTable.clear();
    LeftT0IndexTable.clear();
    LeftDiffClockTable.clear();
    LeftEdbEvents.clear();

    _ByteOfEvent = 8;
    _MessageTag = "UtsusemiRedisPubSubEventReader::";

    _prePid=0;
    _preClock=0.0;
    _firstClock=0.0;

    isDebug=false;
}
//////////////////////////////////////////////////////////
bool UtsusemiRedisPubSubEventReader::
Subscribe( std::string instCode, UInt4 runNo, UInt4 daqId, UInt4 modNo, std::string host, UInt4 port, std::string date ){
    return Subscribe( instCode, _makeKeyRoot( instCode, runNo, daqId, modNo, date ), host, port );
}
//////////////////////////////////////////////////////////
bool UtsusemiRedisPubSubEventReader::
Subscribe( std::string inst_code, std::string keyRoot, std::string host, UInt4 port ){
    if (_RedisPubSubEdb!=NULL){
        UtsusemiWarning(_MessageTag+"Subscribe >> Not yet Unsubscribe." );
        UtsusemiWarning(_MessageTag+"Subscribe >> Done Unsubscribe all." );
        Unsubscribe();
    }

    _KeyOfEdb = inst_code+":edb:"+keyRoot+"_edb";
    _KeyOfT0b = inst_code+":t0b:"+keyRoot+"_t0b";

    _RedisPubSubT0b = new RedisSyncSubscriber(host, port);
    _RedisPubSubEdb = new RedisSyncSubscriber(host, port);

    if (((_RedisPubSubEdb->init())<0)||((_RedisPubSubT0b->init())<0)){
        UtsusemiError( _MessageTag+"Subscribe >> initSyncSubscriber failed." );
        Unsubscribe();
        return false;
    }

    bool isEdbSubReady = false;
    bool isT0bSubReady = false;

    if ((_RedisPubSubEdb->subscribe( _KeyOfEdb ))==0){
        isEdbSubReady=true;
    }
    if ((_RedisPubSubT0b->subscribe( _KeyOfT0b ))==0){
        isT0bSubReady=true;
    }
    if (isEdbSubReady&&isT0bSubReady){
        UtsusemiMessage( _MessageTag+"Subscribe >> Subscribed Key = "+_KeyOfEdb );
        UtsusemiMessage( _MessageTag+"Subscribe >> Subscribed Key = "+_KeyOfT0b );
        return true;
        }else{
        if (!isEdbSubReady){
            UtsusemiError( _MessageTag+"Subscribe >> Subscribe EDB failed. key="+_KeyOfEdb );
            _RedisPubSubEdb->unsubscribe( _KeyOfEdb );
            _KeyOfEdb="";
        }

        if (!isT0bSubReady){
            UtsusemiError( _MessageTag+"Subscribe >> Subscribe T0B failed. key="+_KeyOfT0b );
            _RedisPubSubT0b->unsubscribe( _KeyOfT0b );
            _KeyOfT0b="";
        }
    }
    return false;

}
//////////////////////////////////////////////////////////
bool UtsusemiRedisPubSubEventReader::
Unsubscribe(){
    if (_RedisPubSubEdb!=NULL){
        if (_KeyOfEdb!=""){
            _KeyOfEdb="";
            if (_RedisPubSubEdb->unsubscribe( _KeyOfEdb )<0) return false;
        }
        delete _RedisPubSubEdb;
        _RedisPubSubEdb=NULL;
        if (_KeyOfT0b!=""){
            _KeyOfT0b="";
            if (_RedisPubSubT0b->unsubscribe( _KeyOfT0b )<0) return false;
        }
        delete _RedisPubSubT0b;
        _RedisPubSubT0b=NULL;
    }
    return true;
}
//////////////////////////////////////////////////////////
std::string UtsusemiRedisPubSubEventReader::
_makeKeyRoot( std::string instCode, UInt4 runNo, UInt4 daqId, UInt4 modNo, std::string date){
    /*
    if (date==""){
        time_t timer;
        time(&timer);
        struct tm *t_st = localtime( &timer );
        char date_c[10];
        std::sprintf( date_c, "%4d%02d%02d",
                 t_st->tm_year+1900,
                 t_st->tm_mon+1,
                 t_st->tm_mday );
        std::string tmp(date_c);
        date=tmp;
    }
    char key_c[256];

    std::sprintf( key_c, "%3s%06d_%8s_%3s%06d_%02d_%03d", instCode.c_str(), runNo, date.c_str(), instCode.c_str(), runNo, daqId, modNo );
    */
    char key_c[256];
    std::sprintf( key_c, "%3s%06d_%02d_%03d", instCode.c_str(), runNo, daqId, modNo );
    std::string key( key_c );
    return key;
}
//////////////////////////////////////////////////////////
bool UtsusemiRedisPubSubEventReader::
_getOfT0b( std::vector<UInt8> &t0Index, std::vector<UInt8> &t0Pid, std::vector<Double> &t0Clk ){
    Char* buf_in = new Char[ REDIS_PUBSUB_BUF_SIZE ];
    UChar* t0_buf = new UChar[_ByteOfEvent];
    std::string key = "";
    Int4 size = 0;
    UInt8 num_events=0;
    Double dumm=0.0;
    UInt8 pid = _prePid;
    Double pclock = _preClock;
    //FILE* fo = fopen( "test.t0b","ab" );
    //while( true ){
    UInt4 k=0;
    Int4 smm = 0;
    clock_t t1,t2;

    while( k<1000 ){
        k++;
        t1=clock();
        Int4 ret=_RedisPubSubT0b->get(key, buf_in, size, REDIS_PUBSUB_TIMEOUT);
        t2=clock();
        smm+=(t2-t1);
        if (ret==0){ // get data

            for (UInt4 i=0; i<size; i+=(_ByteOfEvent)){
                for (UInt4 j=0; j<(_ByteOfEvent); j++) t0_buf[j]=buf_in[i+j];
                //std::fwrite( t0_buf, 1, 8, fo );
                if (t0_buf[0]==0x4b){
                    decodePidEvent( t0_buf, &pid );
                }else if ((t0_buf[0]==0x4a)||(t0_buf[0]==0x4f)){
                    if (t0_buf[0]==0x4f)
                        decodeT0InfoEvent(t0_buf,&num_events);
                    else
                        decodeT0InfoEvent(t0_buf,&num_events,&dumm);

                    t0Index.push_back(num_events);
                    t0Pid.push_back(pid);
                    pid++;
                }else if ((t0_buf[0]==0x4d)||(t0_buf[0]==0x4e)){
                    Double clock_inc;
                    decodeClockIncEvent( t0_buf, &clock_inc );
                    t0Clk.push_back(clock_inc);
                    pclock+=clock_inc;
                }else if (t0_buf[0]==0x4c){
                    decodeT0ClockEvent(t0_buf,&pclock);
                    if (_firstClock==0.0) _firstClock=pclock;
                }else{
                    char t=t0_buf[0];
                    std::string tmp(&t);
                    UtsusemiWarning( _MessageTag+"_getOfT0b >> Other events "+tmp );
                }
            }
        }else if (ret<0){// Error
            UtsusemiError(_MessageTag+" Get Error" );
            delete [] buf_in;
            delete [] t0_buf;
            return false;
        }else{ // ret==1 : time out
            break;
        }
    }
    if (isDebug){
      if (k==1000) std::cout << "## LOOP MAX time"<< std::endl;
      std::cout << " ave of Subscriber::get ="<<smm/double(k) << std::endl;
    }
    _prePid=pid;
    _preClock=pclock;
    delete [] buf_in;
    delete [] t0_buf;
    //fclose( fo );

    return true;

}
//////////////////////////////////////////////////////////
bool UtsusemiRedisPubSubEventReader::
_getOfEdb( Char *buf, UInt4 &size_buf, UInt4 &ind, std::vector<UInt8> &t0Index ){
    std::string key;
    Int4 size;

    Char* leftEdbBuf;
    UInt4 size_leftEdbBuf=0;
    bool isOut = false;
    //FILE* fo = fopen( "test.edb","ab" );
    while( true ){
        Int4 ret = 0;
        Char* buf_edb = new Char[ REDIS_PUBSUB_BUF_SIZE ];
        if (LeftEdbEvents.empty()){
            ret=_RedisPubSubEdb->get(key, buf_edb, size, REDIS_PUBSUB_TIMEOUT);
            //if (ret==0) std::fwrite( buf_edb, 1, size,fo);
        }else{
            size = LeftEdbEvents.size();
            for (UInt4 i=0; i<size; i++)
                buf_edb[i]=LeftEdbEvents[i];
            LeftEdbEvents.clear();
        }

        if (ret==0){ // get data
            while( (size_buf+size)>((t0Index[ind]-t0Index[0])*_ByteOfEvent) ){
                ind++;
                if (ind==t0Index.size()){
                    isOut=true;
                    break;
                }
            }
            if (ind>0) ind--;
            if (isOut){
                UInt4 enabled_size = (t0Index[ind]-t0Index[0])*_ByteOfEvent - size_buf;
                for (UInt4 i=0; i<enabled_size; i++)
                    buf[i+size_buf]=buf_edb[i];
                size_leftEdbBuf = size - enabled_size;
                if (size_leftEdbBuf>0){
                    leftEdbBuf = new Char[ size_leftEdbBuf ];
                    for (UInt4 i=0; i<size_leftEdbBuf; i++)
                        leftEdbBuf[i]=buf_edb[enabled_size + i];
                }
                size_buf += enabled_size;
                delete [] buf_edb;
                break;
            }
            for (UInt4 i=0; i<size; i++)
                buf[size_buf+i]=buf_edb[i];
            size_buf += size;

        }else if (ret==-1){// Error
            UtsusemiError(_MessageTag+" Get Error" );
            delete [] buf_edb;
            return false;
        }else{ // ret==1 : time out
            delete [] buf_edb;
            break;
        }
        delete [] buf_edb;
    }
    //fclose( fo );

    LeftEdbEvents.clear();
    if (size_leftEdbBuf>0){
        LeftEdbEvents.resize(size_leftEdbBuf);
        for (UInt4 i=0; i<size_leftEdbBuf; i++) LeftEdbEvents[i]=leftEdbBuf[i];
        delete [] leftEdbBuf;
    }
    return true;
}
//////////////////////////////////////////////////////////
bool UtsusemiRedisPubSubEventReader::
Read( UChar *data, UInt4 *num_of_data, std::vector<UInt8> *t0PulseId, std::vector<Double> *t0DiffClock, std::vector<UInt8> *t0Index ){

    std::vector<UInt8> tmp_T0b( LeftT0IndexTable.size() );
    std::vector<UInt8> tmp_T0bPid( LeftPulseIdTable.size() );
    std::vector<double> tmp_T0bClk( LeftDiffClockTable.size() );
    copy( LeftT0IndexTable.begin(), LeftT0IndexTable.end(), tmp_T0b.begin() );
    copy( LeftPulseIdTable.begin(), LeftPulseIdTable.end(), tmp_T0bPid.begin() );
    copy( LeftDiffClockTable.begin(), LeftDiffClockTable.end(), tmp_T0bClk.begin() );

    if (isDebug) std::cout << "Reader::Reader _getOfT0b" << std::endl;
    bool ret=_getOfT0b( tmp_T0b, tmp_T0bPid, tmp_T0bClk );
    if (!ret) return false;

    if (tmp_T0b.size()==0){
        (*num_of_data)=0;
        UtsusemiWarning( _MessageTag+"Read >> _getOfT0b is empty." );
        return true;
    }

    UInt4 max_num_events = tmp_T0b.back()-tmp_T0b[0]  + LeftEdbEvents.size();
    Char* buf = new Char[ max_num_events*_ByteOfEvent ];
    UInt4 size_buf = 0;
    UInt4 frameNo = 0;
    if (isDebug) std::cout << "Reader::Reader _getOfEdb" << std::endl;
    ret = _getOfEdb( buf, size_buf, frameNo, tmp_T0b );
    if (!ret) return false;

    t0PulseId->clear();
    t0DiffClock->clear();
    t0Index->clear();
    t0PulseId->resize( frameNo );
    t0DiffClock->resize( frameNo );
    t0Index->resize( frameNo );

    for (UInt4 i=0; i<size_buf; i++) data[i]=buf[i];
    delete [] buf;

    (*num_of_data) = size_buf;
    t0PulseId->assign( tmp_T0bPid.begin(), tmp_T0bPid.begin()+frameNo );
    t0DiffClock->assign( tmp_T0bClk.begin(), tmp_T0bClk.begin()+frameNo );
    //for (UInt4 i=0; i<frameNo; i++) t0Index->at(i) = tmp_T0b[i+1]-tmp_T0b[i];
    t0Index->assign( tmp_T0b.begin(), tmp_T0b.begin()+frameNo );

    LeftT0IndexTable.clear();
    LeftPulseIdTable.clear();
    LeftDiffClockTable.clear();
    if ( (tmp_T0b.size()-1)>=frameNo ){
        LeftT0IndexTable.resize( tmp_T0b.size()-frameNo );
        LeftPulseIdTable.resize( tmp_T0bPid.size()-frameNo );
        LeftDiffClockTable.resize( tmp_T0bClk.size()-frameNo );
        copy( tmp_T0b.begin()+frameNo, tmp_T0b.end(), LeftT0IndexTable.begin() );
        copy( tmp_T0bPid.begin()+frameNo, tmp_T0bPid.end(), LeftPulseIdTable.begin() );
        copy( tmp_T0bClk.begin()+frameNo, tmp_T0bClk.end(), LeftDiffClockTable.begin() );
    }

    return true;

}
//////////////////////////////////////////////////////////
void UtsusemiRedisPubSubEventReader::
decodeT0InfoEvent(UChar event[], UInt8 *num, Double *clock) {
    UInt4 temp1,temp2;
    temp1 = (UInt4)(event[1]<<7) + (UInt4)(event[2]>>1);
    temp2 = (UInt4)( (event[2]&0x1)<<9 ) + (UInt4)(event[3]<<1) + (UInt4)(event[4]>>7);
    *clock = (Double)temp1 + ((Double)temp2/1000.);

    temp1 = (UInt4)((event[4]&0x7f)<<24) + (UInt4)(event[5]<<16);
    temp2 = (UInt4)(event[6]<<8) + (UInt4)(event[7]);
    *num = temp1 + temp2;
}
//////////////////////////////////////////////////////////
void UtsusemiRedisPubSubEventReader::
decodeT0InfoEvent(UChar event[], UInt8 *num ) {
    *num = (((UInt8)event[2])<<40) + (((UInt8)event[3])<<32) + ((UInt8)(event[4])<<24)
        + (UInt8)(event[5]<<16) + (UInt8)(event[6]<<8) + (UInt8)(event[7]);
}
//////////////////////////////////////////////////////////
void UtsusemiRedisPubSubEventReader::
decodeClockIncEvent(UChar event[], Double *clock) {
    double cnt = 40000000.0;
    if (event[0]==0x4e) cnt = 1000000000.0;
    *clock = (Double)( ( (UInt8)(event[2])<<40 )
                       + ( (UInt8)(event[3])<<32 )
                       + ( (UInt8)(event[4])<<24 )
                       + ( (UInt8)(event[5])<<16 )
                       + ( (UInt8)(event[6])<<8 )
                       + ( (UInt8)(event[7] ) )
        )/cnt;
}
//////////////////////////////////////////////////////////
void UtsusemiRedisPubSubEventReader::
decodePidEvent(UChar eventPid[], UInt8 *pid ){
    /* decode PulseId (40bit) */
    *pid=((UInt8)eventPid[3]<<32)
        +((UInt4)eventPid[4]<<24)
        +((UInt4)eventPid[5]<<16)
        +((UInt4)eventPid[6]<<8)
        +(UInt4)eventPid[7];
}
//////////////////////////////////////////////////////////
void UtsusemiRedisPubSubEventReader::
decodeT0ClockEvent(UChar eventClock[], Double *clock){
    UInt4 sec;
    Double subsec;
    Double submicrosec;

    /* decode Second from 2008/1/1 (30bit) */
    sec=((UInt4)eventClock[1]<<22)
        +((UInt4)eventClock[2]<<14)
        +((UInt4)eventClock[3]<<6)
        +((UInt4)eventClock[4]>>2);

    /* decode sub-Second (15bit) */
    subsec=(Double)(
        ((UInt4)(eventClock[4]&3)<<13)
        +((UInt4)eventClock[5]<<5)
        +((UInt4)eventClock[6]>>3)
        )/32768.;
    /* decode sub-microSecond (11bit) */
    submicrosec=(Double)(
        ((UInt4)(eventClock[6]&7)<<8)
        +(UInt4)eventClock[7]
        )/40000000.;
    *clock=(Double)sec + subsec + submicrosec;
}
//////////////////////////////////////////////////////////
bool UtsusemiRedisPubSubEventReader::
Test( std::string inst_code, UInt4 runNo, UInt4 daqId, UInt4 modNo, std::string saveFile, std::string host, UInt4 port, UInt4 loop ){

    if (Subscribe(inst_code, runNo, daqId, modNo, host, port )){
        UtsusemiMessage( _MessageTag+"Test >> Subscribe succeeded." );
    }else{
        UtsusemiError( _MessageTag+"Test >> Subscribe fails" );
        return false;
    }

    std::vector<UInt8> retPid;
    std::vector<Double> retClk;
    std::vector<UInt8> retInd;

    std::vector<UInt8> AllPid;
    std::vector<Double> AllClk;
    std::vector<UInt8> AllInd;
    UInt4 sizeAllPid=0;
    UInt4 sizeAllClk=0;
    UInt4 sizeAllInd=0;

    FILE* fp;
    if(NULL==(fp= fopen(saveFile.c_str(),"wb"))){
        UtsusemiError( _MessageTag+"Test >> Can't open " +saveFile+ "." );
        return false;
    }

    UtsusemiMessage( _MessageTag+"Test >> Waiting 5 seconds to start" );
    Int4 ret=sleep(5);

    for (UInt4 i=0; i<loop; i++){
        UChar* data = new UChar [REDIS_PUBSUB_BUF_SIZE];
        UInt4 num_of_data=0;
        if (Read( data, &num_of_data, &retPid, &retClk, &retInd )){
            UtsusemiMessage( _MessageTag+"Test >> Read succeeded." );
        }else{
            UtsusemiError( _MessageTag+"Test >> Read fails." );
            return false;
        }

        UChar buf[8];
        for (UInt4 i=0;i<num_of_data;i+=8){
            for (UInt4 j=0;j<8;j++) buf[j]=data[i+j];
            std::fwrite( buf, 1, 8, fp );
        }
        delete [] data;

        AllPid.resize(sizeAllPid+retPid.size(),0);
        AllClk.resize(sizeAllClk+retClk.size(),0.0);
        AllInd.resize(sizeAllInd+retInd.size(),0);
        copy( retPid.begin(), retPid.end(), AllPid.begin()+sizeAllPid );
        copy( retClk.begin(), retClk.end(), AllClk.begin()+sizeAllClk );
        copy( retInd.begin(), retInd.end(), AllInd.begin()+sizeAllInd );
        sizeAllPid = AllPid.size();
        sizeAllClk = AllClk.size();
        sizeAllInd = AllInd.size();
    }
    fclose(fp);

    bool retVal=false;
    if (Unsubscribe()){
        UtsusemiMessage( _MessageTag+"Test >> Unsubscribe succeeded." );
        retVal=true;
    }else{
        UtsusemiError( _MessageTag+"Test >> Unsubscribe FAILED." );
    }

    std::cout << "---------------------------------------------"<< std::endl;
    std::cout << " AllPid = " << std::endl;
    for (UInt4 i=0; i<AllPid.size(); i++) std::cout << AllPid[i] << ",";
    std::cout << std::endl;
    std::cout << "---------------------------------------------"<< std::endl;
    std::cout << " AllClk = " << std::endl;
    for (UInt4 i=0; i<AllClk.size(); i++) std::cout << AllClk[i] << ",";
    std::cout << std::endl;
    std::cout << "---------------------------------------------"<< std::endl;
    std::cout << " AllInd = " << std::endl;
    for (UInt4 i=0; i<AllInd.size(); i++) std::cout << AllInd[i] << ",";
    std::cout << std::endl;

    return retVal;
}
//////////////////////////////////////////////////////////
bool UtsusemiRedisPubSubEventReader::
simpleTest( std::string inst_code, std::string keyRoot, std::string saveFile, std::string host, UInt4 port ){

    RedisSyncSubscriber p(host, port);
    p.init();
    p.subscribe( keyRoot );

    FILE* fp;
    if(NULL==(fp= fopen(saveFile.c_str(),"wb"))){
        UtsusemiError( _MessageTag+"saveT0IndexEvent >> Can't open " +saveFile+ "." );
        return false;
    }


    Char* data = new Char [REDIS_PUBSUB_BUF_SIZE];
    Int4 size = 0;
    Int4 ret=1;
    while(ret==1){
        ret=p.get( keyRoot, data, size, REDIS_PUBSUB_TIMEOUT);
        if (ret==0){
            UChar buf[8];
            for (UInt4 i=0;i<size;i+=8){
                for (UInt4 j=0;j<8;j++) buf[j]=data[i+j];
                std::fwrite( buf, 1, 8, fp );
            }
        }
    }

    fclose(fp);
    delete [] data;

    return true;
}
