//
//  eventLogger.mm
//  oscSenderExample
//
//  Created by Robert Tubb on 05/11/2012.
//
//

//---------------------------------------------------------------------------
#include "eventLogger.h"

EventLogger eventLogger;

//---------------------------------------------------------------------------
EventLogger::EventLogger(){
    
    consentGiven = true; // unless told otherwise firstAppOpen
    loggingEnabled = true;
    serverConnectionOK = false;
    questionnaireCompleted = false;
    questionnaireUploaded = false;
    logUploadInProgress = false;
    
    nextUploadQty = UPLOAD_CHUNK_SIZE; // amount of data uploaded is always more than UPLOAD_CHUNK_SIZE events
    serverComms = [[ServerComms alloc] init];
    
}
// for RIFTATHON this replaces startLoadAll
// because we can't look at logs or presets until we know user

void EventLogger::onUsernameEntered(){
    sessionStartTime = ofGetSystemTime();
    // userName = name; done already by dialog
    // check for eventlog with this name

    
    string path = ofxiPhoneGetDocumentsDirectory();
    ofDirectory dir(path);
    //only show png files
    dir.allowExt("json");
    //populate the directory object
    dir.listDir();
    
    //chek for username's logs
    bool userHasLogs = false;
    int numExistingLogs = 0;
    for(int i = 0; i < dir.numFiles(); i++){
        string fname = dir.getName(i);
        if( 0 == fname.compare(0, userName.length() + 10, userName + "_eventlog_" )){
            numExistingLogs++;
            userHasLogs = true;
        }
        
    }
    // if found, load other details such as device id
    loadUserDetailsFromLastLogFile(numExistingLogs);
    nextLogFileIndex = numExistingLogs+1;
    
}

//---------------------------------------------------------------------------
void EventLogger::startLoadAll(){
    
    // TODO pilot doesn't check existing
    //loadExistingLogFile(ofxiPhoneGetDocumentsDirectory() + EVENT_LOG_FILENAME);
    
    
    sessionStartTime = ofGetSystemTime();
    
    // TODO pilot
    // testConnection();
    
    // if we have a back log of events upload them
    if(theEvents.size() > nextUploadQty && ! logUploadInProgress){
        //try to upload
        
        // TODO pilot doesn't upload
        //uploadEventLog(true);
    }
    
    if(questionnaireCompleted && !questionnaireUploaded){
        uploadQuestionnaire();
    }
    
    //TODO if questionnaire wasn't reached but still opened then set the timer to something sensible
    
    //timer.setInteractionTime(savedInteractionTime);
    //timer.setOrderFromPrevious(interfaceOrder);
    
    // for now sod it, start from scratch
    if(!questionnaireCompleted){
        cout<<"Questionnaire NOT completed - first APP open called\n";
        firstEverAppOpen();
    }
 
}
//---------------------------------------------------------------------------
void EventLogger::testConnection(){
    Json::Value root;
    root["x"] = "y";
    cout << "testConnection\n";
    sendToServer("testConnection", root, true);
    
}
//---------------------------------------------------------------------------

void EventLogger::loadUserDetailsFromLastLogFile(int numExistingLogs){
    
    
}
//---------------------------------------------------------------------------


// this reads the persistent log file , checks if we've used the app before and
// if we've answered questionnaire or not

// should just store stuff. the start up state should be handled by testAPp

void EventLogger::loadExistingLogFile(const string &jsonFile){
    Json::Value root;
    Json::Reader reader;
    
    /////////////
    // read file
    
    ifstream theFile(jsonFile.c_str());
    stringstream fileText;
    string line;
    if(!theFile){
        cout<<"No event log file found - first APP open called\n";
        firstEverAppOpen();
        return;
    }else{
        while(theFile){
            theFile >> line;
            // cout << line;  // lots!!!!
            fileText << line;
        }
        theFile.close();
    }
    
    cout << "size of log JSON string:" << fileText.str().length() << "BYTES \n";
    
    bool parsingSuccessful = reader.parse( fileText.str(), root );
    
    if ( !parsingSuccessful )
    {
        // report to the user the failure and their locations in the document.
        std::cout  << "Failed to parse event log JSON: \n"
        << reader.getFormattedErrorMessages();
        return;
    }
    
    /////////////////
    // now put user deets into variables
    
    userName = root["userName"].asString();
    deviceID = root["deviceID"].asLargestInt();
    nextUploadNumber = root["uploadNumber"].asInt();
    savedInteractionTime = root["savedInteractionTime"].asLargestInt();
    questionnaireCompleted = root["questionnaireCompleted"].asBool();
    questionnaireUploaded = root["questionnaireUploaded"].asBool();
    
    // check for unuploaded evts
    const Json::Value jlogs = root["events"];
    
    for ( int index = 0; index < jlogs.size(); ++index ) theEvents.push_back(lEvent(jlogs[index]));
    
    
    //////////////
    
    if(questionnaireCompleted && !questionnaireUploaded){
        // then read it in and upload it
        Json::Value JQ = root["questionnaire"];
        Json::Value JArray = JQ["qAnswers"];
        if(JArray.size() < 2){
            cout << "Error - status of questionnaire is wierd\n";
        }
        for ( unsigned int i = 0; i < JArray.size(); i++ )
        {
            questionnaireAnswers.push_back(JArray[i].asInt());
        }
        questionnaireComments = JQ["comments"].toStyledString();
        //uploadQuestionnaire();
    }
    
    // TODO interaction time seems to be balls
    cout << "Total interaction time: " << savedInteractionTime << '\n';
    
}

//---------------------------------------------------------------------------

void EventLogger::firstEverAppOpen(){
    
    nextUploadNumber = 0;
    deviceID = ofGetSystemTimeMicros();
    savedInteractionTime = 0;
    questionnaireCompleted = false;
    questionnaireUploaded = false;
    
}


//---------------------------------------------------------------------------
void EventLogger::questionnaireAnswersObtained(vector<int> answers, const char* userComments){
    
    questionnaireCompleted = true;
    questionnaireAnswers = answers;
    questionnaireComments = userComments;
    uploadQuestionnaire();
    logEvent(QUESTIONNAIRE_COMPLETED);
    
}
//---------------------------------------------------------------------------
void EventLogger::uploadQuestionnaire(){
    // show indicator
    cout << "^^^^^^^^ UPLOADING QUESTIONNAIRE ^^^^^^^^ \n";
    cout << questionnaireToJson() << "\n";
    sendToServer("questionnaire", questionnaireToJson(), true);
    
}
//---------------------------------------------------------------------------
bool EventLogger::sendToServer(string functionName, Json::Value jsonData, bool async = false){
    
    Json::FastWriter writer;
    string jsontext = writer.write( jsonData );
    
    // remove newline
    if (!jsontext.empty() && jsontext[jsontext.length()-1] == '\n') {
        jsontext.erase(jsontext.length()-1);
    }
    ostringstream jd;
    jd << jsontext;
    NSString *theData = [NSString stringWithUTF8String:jd.str().c_str()];
    NSString *theType = [NSString stringWithUTF8String:functionName.c_str()];
    
    if(async){
        [serverComms doPostRequest:theType withData:theData];
    }else{
        bool success = [serverComms doSyncPostRequest:theType withData:theData];
        return success;
    }
    
}
//-----------------------------
void EventLogger::questionnaireOK(){
    questionnaireUploaded = true;
    questionnaireComments = "";
}
//-----------------------------
void EventLogger::eventlogOK(){
    // COMMENT THIS IF UPLAODING FROM IPAD TO XCODE
    
    // it's a bad idea to do this in another thread...
    theEvents.clear();
    cout << "EVENT LOG UPLOAD SUCCESS\n";
    nextUploadNumber++;
    logUploadInProgress = false;
}
//-----------------------------
void EventLogger::testConnectionOK(){
    cout << "^^^^^^^^ server connection OK ^^^^^^^^ \n";
    serverConnectionOK = true;
}
//-----------------------------
void EventLogger::questionnaireNotOK(){
    cout << "XXXXX questionnaire  NOT OK XXXXXXX \n";
    questionnaireUploaded = false;
}
//-----------------------------
void EventLogger::eventlogNotOK(){
    // try later
    cout << "XXXXX event log  NOT OK XXXXXXX \n";
    nextUploadQty += UPLOAD_CHUNK_SIZE;
    logUploadInProgress = false;
}
//-----------------------------
void EventLogger::testConnectionNotOK(){
    cout << "XXXXX server connection NOT OK XXXXXXX \n";
    serverConnectionOK = false;
    // alert?
    
}


//---------------------------------------------------------------------------

bool EventLogger::uploadEventLog(bool async){
    
    // show indicator
    logUploadInProgress = true;
    cout << "^^^^^^^^  ATTEMPTING TO UPLOAD " << theEvents.size() << " EVENTS ^^^^^^^^ .\n";
    if(!async){
        bool success = sendToServer("eventlog", logsToJson(), async);
        if(!success){
            // try later
            nextUploadQty += UPLOAD_CHUNK_SIZE;
            // increment upload number?
        }else{
            
            // if success - clear memory
            // IF UPLAODING FROM IPAD TO XCODE COMMENT OUT
            theEvents.clear();
            cout << "UPLOAD SUCCESS\n";
            nextUploadNumber++;
        }
        logUploadInProgress = false;
        return success;
    }else{
        sendToServer("eventlog", logsToJson(), async);
    }
}
//---------------------------------------------------------------------------
// only called when doing supervised tests
void EventLogger::newUser(){
    // store old stuff
    
    saveSessionToFile();
    cout<<"setup new user\n";
    deleteLogs();
    nextUploadNumber = 0;
    deviceID = ofGetSystemTimeMicros();
    savedInteractionTime = 0;
    totalInteractionTime = 0;
    sessionStartTime = ofGetSystemTime();
    questionnaireCompleted = false;
    questionnaireUploaded = false;
    
    //((testApp *)ofGetAppPtr())->showIntro();
    
}
//---------------------------------------------------------------------------
// called from alertView OK in iViewController
void EventLogger::setUsername(const char *u){
    userName = u;
    
}
//---------------------------------------------------------------------------
//new tweakathlon event logger with vector
void EventLogger::logEvent(const leventType& evtType, const vector<int> eventData){
    Poco::Mutex::ScopedLock lock(_mutex);
    if(!loggingEnabled) return;
    
    switch ( evtType ) {
            
//        case CANDIDATE_PARAM_ADJUSTED:
//            // TODO thinning here. maybe. was a pain in the butt.
//            thinnedSliderEvent(lEvent(evtType,eventData));
//            break;
        default:
            // default is just an event type with vector of ints. matlab will know what they are.
            
            
            theEvents.push_back(lEvent(evtType, eventData));
          
            break;
    }
    
    // new riftathon save as we go
    if (theEvents.size() > SAVE_CHUNK_SIZE){
        saveSessionToFile();
        theEvents.clear();
    }
    
    //TODO thiswrong?
    totalInteractionTime = savedInteractionTime + (ofGetSystemTime() - sessionStartTime); // milliseconds
    
}
//---------------------------------------------------------------------------
// OLD SONIC ZOOM EVT - still used for simple events with no data
void EventLogger::logEvent(const leventType& evtType){
    Poco::Mutex::ScopedLock lock(_mutex);
    if(!loggingEnabled) return;
    
    switch ( evtType ) {
            // data thinning here
        case APP_LOADED:
            theEvents.push_back(lEvent(evtType));
        default:
            // default is just an event type with vector of ints. matlab will know what they are.

         
            theEvents.push_back(lEvent(evtType));
          
    }
    
    if(theEvents.size() > nextUploadQty && !logUploadInProgress){
        //try to upload asynchronously
        // TODO pilot doesn't upload
        //uploadEventLog(true);
    }
    
    // new riftathon save as we go
    if (theEvents.size() > SAVE_CHUNK_SIZE){
        saveSessionToFile();
    }
    
    //TODO thiswrong?
    totalInteractionTime = savedInteractionTime + (ofGetSystemTime() - sessionStartTime); // milliseconds
    
}
// UnaryPredicate
template <class T>
bool EventLogger::matchID(T thing){
    return true;
}
bool EventLogger::matchID2(){
    return true;
}
//---------------------------------------------------------------------------
void EventLogger::thinnedSliderEvent(lEvent newEvent){
    static vector<lEvent> unloggedEventsCache; // list of all different slider events
    static int eventCounter = 0;
    
    // if first event then log it.
    if(theEvents.size() == 0){
        theEvents.push_back(newEvent);
    }
    
    // look for last event in the cache that had this mappingID
    cout << unloggedEventsCache.size() << endl;
    
    vector<lEvent>::iterator lastMatching;
    for(lastMatching = unloggedEventsCache.begin();
        lastMatching != unloggedEventsCache.end() && ( (*lastMatching).eventData[0] != newEvent.eventData[0]);
        lastMatching++);
    
    
    if (lastMatching != unloggedEventsCache.end()){
        //cout << "matching id logevent: " << (*lastMatching).eventData[0] << " " << newEvent.eventData[0] << endl;
        // we have another, check gap
        int gap = newEvent.eventTime - (*lastMatching).eventTime;
        if(gap > 300){
            theEvents.push_back((*lastMatching));
            
            // also log new slider evt
            theEvents.push_back(newEvent);
            eventCounter = 0;
            
        }else if(eventCounter >= EVENT_THIN_FACTOR){ // otherwise only record every Nth event
            theEvents.push_back(newEvent);
            eventCounter = 0;
            // DELETE THAT PREVIOUS EVENT
            unloggedEventsCache.erase(lastMatching);
            
        }else{
            // ignore for now but put in unlogged
            unloggedEventsCache.push_back(newEvent);
        }

    }else{
        // this event type wasnt in the cache
        if(eventCounter >= EVENT_THIN_FACTOR){ // otherwise only record every Nth event
            theEvents.push_back(newEvent);
            eventCounter = 0;
        }else{
            unloggedEventsCache.push_back(newEvent);
        }
        
    }
    // always count the new one
    eventCounter++;
   
    
    
}

//--------------------------------------------------------------------
// called from newUser
void EventLogger::deleteLogs(){
    return;
    // the
    theEvents.clear();
    string fname = ofxiPhoneGetDocumentsDirectory() + EVENT_LOG_FILENAME;
    ofFile logFile(fname,ofFile::WriteOnly);
    logFile << "";
    logFile.close();
}

//---------------------------------------------------------------------------

Json::Value EventLogger::logsToJson(){
    // put all logged events into Json formatted string
    Json::Value root;
    
    vector<lEvent>::iterator eventIter;
    
    root["programVersion"] = PROGRAM_VERSION;
    root["userName"] = userName;
    root["deviceID"] = deviceID;
    root["uploadNumber"] = nextUploadNumber;
    root["savedInteractionTime"] = savedInteractionTime;
    root["questionnaireCompleted"] = questionnaireCompleted;
    root["questionnaireUploaded"] = questionnaireUploaded;
    
    // this can mess up matlab script if it doesn't find this field
    root["questionnaire"] = questionnaireToJson();
    
    
    int i = 0;
    for(eventIter = theEvents.begin(); eventIter < theEvents.end(); eventIter++){
        root["events"][i] = (*eventIter).eventToJson();
        i++;
    }
    root["numEventsHere"] = i;
    return root;
}

//---------------------------------------------------------------------------

Json::Value EventLogger::questionnaireToJson(){
    // put all answers into Json formatted string
    Json::Value root;
    
    vector<int>::iterator aIter;
    
    Json::Value questionnaire;
    
    int i = 0;
    for(aIter = questionnaireAnswers.begin(); aIter < questionnaireAnswers.end(); aIter++){
        questionnaire[i] = (*aIter);
        i++;
    }
    
    root["qAnswers"] = questionnaire;
    root["comments"] = questionnaireComments;
    root["userName"] = userName;
    root["deviceID"] = deviceID;
    root["programVersion"] = PROGRAM_VERSION;
    
    return root;
}

//---------------------------------------------------------------------------
void EventLogger::printAll(){
    cout << "-----------------ALL LOGGED EVENTS----------------- \n";
    vector<lEvent>::iterator evIter;
    cout << logsToJson() << "\n";
    cout << "---------------------QUESTIONNAIRE---------------- \n";
    cout << questionnaireToJson() << "\n";
};
//---------------------------------------------------------------------------

void EventLogger::saveSessionToFile(){
    stringstream fn;
    fn << ofxiPhoneGetDocumentsDirectory() << userName << "_eventlog_" << nextLogFileIndexString() << ".json";
    
    string fname = fn.str(); //ofxiPhoneGetDocumentsDirectory() + userName + '_' + "TESTSAVE.json";
    
    // write to file

    Json::Value jlogs = logsToJson();
    ofFile logFile(fname,ofFile::WriteOnly);
    logFile << jlogs;
    logFile.close();
    nextLogFileIndex++;
}

string EventLogger::nextLogFileIndexString(){
    string num = ofToString(nextLogFileIndex);
    string zero = ofToString(0);
    int numzeros = 4 - num.length();
    for (int i=0; i< numzeros;i++){
        num = zero + num;
    }
    return num;
    
}
//----------------------------------------------------------------------------

// this builds up a file incrementally, which can be recovered on crash
//void EventLogger::appendToFile(){
//    
//    
//}

//---------------------------------------------------------------------------

void EventLogger::exitAndSave(){
    
    saveSessionToFile();
    return;
    
    if(!consentGiven){
        Json::Value jlogs = logsToJson();
        // try to upload TODO (no - might hang and prevent exit???)
        
        // TODO pilot doesn't upload
        //       uploadEventLog(true);
        return;
    }
    
    savedInteractionTime = savedInteractionTime + (ofGetSystemTime() - sessionStartTime);
    // save user details
    string fname = ofxiPhoneGetDocumentsDirectory() + userName + '_' + EVENT_LOG_FILENAME;
    
    // try to upload
    // do it sync because event list needs to be cleared to prevent saving on device
    
    // TODO for pilot store to ipad
    // uploadEventLog(false);
    
    // write to file
    // json without the logs that were uploaded!
    Json::Value jlogs = logsToJson();
    ofFile logFile(fname,ofFile::WriteOnly);
    logFile << jlogs;
    cout << jlogs;
    logFile.close();
    
}