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

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

EventLogger eventLogger;

//---------------------------------------------------------------------------
EventLogger::EventLogger(){
    //QuestionnaireViewController * questionnaireViewController;

    consentGiven = true; // unless told otherwise by introView
    loggingEnabled = true;
    serverConnectionOK = false;
    questionnaireCompleted = false;
    questionnaireUploaded = false;
    currentHTTPRequestID = -1;
    logUploadInProgress = false;
    ofxiPhoneDeviceType iOSdeviceType = ofxiPhoneGetDeviceType();
    cout << "Device: " << iOSdeviceType << '\n';
    
    nextUploadQty = UPLOAD_CHUNK_SIZE; // amount of data uploaded is always more than UPLOAD_CHUNK_SIZE events
    
}
//---------------------------------------------------------------------------
// draw() - show path of last N scroll events  - can be scrubbed along?
// 
//---------------------------------------------------------------------------

void EventLogger::init(){
    
    readJsonToLog(ofxiPhoneGetDocumentsDirectory() + EVENT_LOG_FILENAME);
    sessionStartTime = ofGetSystemTime();
    
    testConnection();

    logEvent(APP_STARTED);

    stringstream r;
    r << LOGGING_SERVER_URL << "testConnection?jsontext=" << "{\"test\":\"test\"}&Content-Length=456";

    NSString *urlstring;
    urlstring = @"http://127.0.0.1:8080/testservice/testConnection?jsontext={\"objc post\":\"obj c post\"}";
    serverComms = [[ServerComms alloc] init];
    [serverComms postRequest:urlstring];

    
    urlstring = nil;
}

//---------------------------------------------------------------------------
void EventLogger::questionnaireAnswersObtained(vector<int> answers, const char* userComments){
    
    questionnaireCompleted = true;
    questionnaireAnswers = answers;
    questionnaireComments = userComments;
    uploadQuestionnaire();
    
}
//---------------------------------------------------------------------------
void EventLogger::uploadQuestionnaire(){
    // show indicator
    cout << "^^^^^^^^ UPLOADING QUESTIONNAIRE ^^^^^^^^ \n";
    
    sendToServer("questionnaire", questionnaireToJson(), true);

}
//---------------------------------------------------------------------------
bool EventLogger::sendToServer(string functionName, Json::Value jsonData, bool async = false){
    bool sent;
    stringstream request;

    request << LOGGING_SERVER_URL << functionName << "?jsontext=";
    /*
    if(functionName == "testConnection"){
         request = "http://127.0.0.1:8080/testservice/testConnection?jsontext=";
    }else if(functionName == "questionnaire"){
         request = "http://127.0.0.1:8080/testservice/questionnaire?jsontext=";
    }else if(functionName == "eventlog"){
         request = "http://127.0.0.1:8080/testservice/eventlog?jsontext=";
    }
     */
    Json::FastWriter writer;
    string jsontext = writer.write( jsonData );
    
    // remove newline
    if (!jsontext.empty() && jsontext[jsontext.length()-1] == '\n') {
        jsontext.erase(jsontext.length()-1);
    }

    request << jsontext;
    
    cout << request.str();
    
    if(!async){
        ofURLFileLoader fileLoader;
        ofHttpResponse resp;
        resp = fileLoader.get(request.str());

        cout << "HTTP STATUS  " << resp.status << "\n";
        cout << "HTTP ERROR  " << resp.error << "\n";
        cout << "HTTP DATA  " << resp.data << "\n"; // ofBuffer
        
        stringstream response;
        response << resp.data;
        
        if (resp.status == 200){
            if(response.str() == "OK"){
                
                sent = true;
            }else{
                // not ok
                // problem serverside
                sent = false;
            }
        }else{
            
            sent = false;
            // SHOW AN ALERT TO USER?
        }
        
        return sent;
    }else{ // async
        ofURLFileLoader fileLoader;
        currentHTTPRequestID = ofLoadURLAsync(request.str(), functionName);
        ofRegisterURLNotification(this);
        
        return true; // ???
    }
}
//-----------------------------
void EventLogger::urlResponse(ofHttpResponse & response){
    cout << "gotHTTPRequestStatus\n";
    cout << "HTTP REQUEST NAME  " << response.request.name << "\n";
    cout << "HTTP STATUS  " << response.status << "\n";
    cout << "HTTP ERROR  " << response.error << "\n";
    cout << "HTTP DATA  " << response.data << "\n"; // ofBuffer
    
    bool sent;
    stringstream respStr;
    respStr << response.data;
    
    if (response.status == 200){
        if(respStr.str() == "OK"){
            
            sent = true;
        }else{
            // not ok
            // problem serverside
            sent = false;
        }
    }else{
        
        sent = false;
        // SHOW AN ALERT TO USER?
    }

    // now do request specific stuff
    if(response.request.name == "eventlog"){
        if(!sent){
            // try later
            nextUploadQty += UPLOAD_CHUNK_SIZE;
        }else{
            
            // if success - clear memory
            theEvents.clear();
            cout << "UPLOAD SUCCESS\n";
            nextUploadNumber++;
        }
        logUploadInProgress = false;
    }else if(response.request.name == "questionnaire"){
        if(sent){
            questionnaireUploaded = true;
        }else{
            questionnaireUploaded = false; // will try next time... when?
        }
    }else if(response.request.name == "testConnection"){
        
        if (sent){
            cout << "^^^^^^^^ server connection OK ^^^^^^^^ \n";
            serverConnectionOK = true;
        }else{
            serverConnectionOK = false;
            cout << "server connection ERROR \n";
        }
    }

}
//---------------------------------------------------------------------------
bool EventLogger::testConnection(){
    Json::Value root;
    root["x"] = "y";
    
    
    sendToServer("testConnection", root, true);

}
//---------------------------------------------------------------------------
// this reads the persistent log file , checks if we've used the app before and
// if we've answered questionnaire or not
void EventLogger::readJsonToLog(const string &jsonFile){
    Json::Value root;
    Json::Reader reader;
    
    
    ifstream theFile(jsonFile.c_str());
    stringstream fileText;
    string line;
    if(!theFile){
        
        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();
    interfaceOrder = root["interfaceOrder"].asInt();
    
    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();
    }
    
    // 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(theEvents.size() > nextUploadQty && ! logUploadInProgress){
        //try to upload
        uploadEventLog(true);
    }
    // TODO if the total interaction time is greater than a certain amount && no questions answered - questionnaire time!
    cout << "Total interaction time: " << savedInteractionTime << '\n';
    
    //timer.setInteractionTime(savedInteractionTime);
    //timer.setOrderFromPrevious(interfaceOrder);
    
}


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

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 : NOPE has maximum size
            nextUploadQty += UPLOAD_CHUNK_SIZE;
        }else{
            
            // if success - clear memory
            theEvents.clear();
            cout << "UPLOAD SUCCESS\n";
            nextUploadNumber++;
        }
        logUploadInProgress = false;
        return success;
    }else{
        sendToServer("eventlog", logsToJson(), async);
    }
}
//----------------------------------------------------------------------------

//----------------------------------------------------------------------------
//void EventLogger::deleteLogFile(){

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

void EventLogger::firstEverAppOpen(){
    cout<<"no event log file - first APP open\n";
    nextUploadNumber = 0;
    deviceID = ofGetSystemTimeMicros();
    savedInteractionTime = 0;
    questionnaireCompleted = false;
    questionnaireUploaded = false;
    
    ((testApp *)ofGetAppPtr())->showIntro();
    consentGiven = false;
    
}
//---------------------------------------------------------------------------
// only called when doing supervised tests
void EventLogger::newUser(){
    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;
    // start interaction clock
    
    //timer.startInteractionClock();
    [((testApp *)ofGetAppPtr())->tsc startTimer];
}
//---------------------------------------------------------------------------
void EventLogger::thinnedLogEvent(lEvent newEvent){
    static lEvent previousEvent(APP_STARTED); // initialised as whatever. hopefully won't log
    static int eventCounter = 0;
    
    // if first event then log it. it won't be, but still.
    if(theEvents.size() == 0){
        theEvents.push_back(newEvent);
        previousEvent = newEvent;
        return;
    }
    
    // if previous event is more than 300ms ago, or event type has changed, log both of them
    int gap = newEvent.eventTime - previousEvent.eventTime;
    if(gap > 300 || newEvent.eventType != previousEvent.eventType){
        // if prev event not logged, log it
        if((*theEvents.end()).eventTime != previousEvent.eventTime){
            theEvents.push_back(previousEvent);
        }
        theEvents.push_back(newEvent);

    }
    
    // otherwise only record every Nth event
    if(eventCounter >= EVENT_THIN_FACTOR){
        theEvents.push_back(newEvent);
        eventCounter = 0;

    }
    eventCounter++;
    previousEvent = newEvent;
}
//---------------------------------------------------------------------------
void EventLogger::logEvent(const leventType& evtType,const TwoVector& centre, const double& scale, const int& sliderID, const double& sliderVal){
    //cout << "log: " << evtType << "\n";
    
    // scroll has 2 double coords
    // zoom has 1 double scale
    // save preset has 2 coords
    // switch view has view type
    // slider change has int slider index and 1 float value
    
    // get time for key index
    
    // thinFactor
    if(!loggingEnabled) return;
    switch ( evtType ) {
            // data thinning here
        case SCROLL:
            thinnedLogEvent(lEvent(evtType,centre.x,centre.y));
            break;
        case ZOOM:
            thinnedLogEvent(lEvent(evtType,scale));
            break;
        case CHANGE_SLIDER:
            thinnedLogEvent(lEvent(evtType,sliderVal , 0.0 , sliderID));
            break;
           // non thinned data
        case SAVE_PRESET:
            theEvents.push_back(lEvent(evtType,centre.x,centre.y));
            // Code
            break;
        case SAVE_DESET:
            theEvents.push_back(lEvent(evtType,centre.x,centre.y));
            break;

        case SCROLL_STOPPED:
            theEvents.push_back(lEvent(evtType,centre.x,centre.y));
            cout << "SCROLL STOPPED EVENT\n";
            break;
        case SNAPPED_TO_PRESET:
            theEvents.push_back(lEvent(evtType,centre.x,centre.y,sliderID));
            cout << "SCROLL STOPPED EVENT\n";
            break;
        case SWAP_VIEW:
            theEvents.push_back(lEvent(evtType,0.0 , 0.0 , sliderID)); // slider ID is which view
            break;
        default:
            // default is just an event type with no values
            theEvents.push_back(lEvent(evtType));
            break;
    }
    if(theEvents.size() > nextUploadQty && !logUploadInProgress){
        //try to upload asynchronously
        uploadEventLog(true);
    }
    //sessionTime = (ofGetSystemTime() - sessionStartTime);
    totalInteractionTime = savedInteractionTime + (ofGetSystemTime() - sessionStartTime); // milliseconds

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

void EventLogger::exitAndSave(){
    
    if(!consentGiven){
        logEvent(CONSENT_DENIED);
        Json::Value jlogs = logsToJson();
        // try to upload TODO (no - might hang and prevent exit???)
        uploadEventLog(true);
        return;
    }
    logEvent(APP_EXITED);
    savedInteractionTime = savedInteractionTime + (ofGetSystemTime() - sessionStartTime);
    // save user details
    string fname = ofxiPhoneGetDocumentsDirectory() + EVENT_LOG_FILENAME;

    // try to upload TODO (no - might hang and prevent exit???)
    // do it async because event list needs to be cleared to prevent saving on device
    uploadEventLog(false);
    
    // write to file
    // json without the logs that were uploaded!
    Json::Value jlogs = logsToJson();
    ofFile logFile(fname,ofFile::WriteOnly);
    logFile << jlogs;
    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["iOSdeviceType"] = iOSdeviceType;
    root["savedInteractionTime"] = savedInteractionTime;
    root["questionnaireCompleted"] = questionnaireCompleted;
    root["questionnaireUploaded"] = questionnaireUploaded;
    if(questionnaireCompleted && !questionnaireUploaded){
        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++;
    }
    cout << questionnaireComments << "\n";

    root["qAnswers"] = questionnaire;
    root["comments"] = questionnaireComments;
    root["userName"] = userName;
    root["deviceID"] = deviceID;
    root["iOSdeviceType"] = iOSdeviceType;
    root["programVersion"] = PROGRAM_VERSION;
    
    return root;
}
//---------------------------------------------------------------------------
//---------------------------------------------------------------------------
//---------------------------------------------------------------------------
