/**
 *
 * @file src/parser/PajeParser/mt_ParserPaje.cpp
 *
 * @copyright 2008-2024 Bordeaux INP, CNRS (LaBRI UMR 5800), Inria,
 *                      Univ. Bordeaux. All rights reserved.
 *
 * @author Camille Ordronneau
 * @author Johnny Jazeix
 * @author Augustin Degomme
 * @author Mathieu Faverge
 *
 * @date 2024-07-17
 */
#include <iostream>
#include <fstream>
#include <string>
#include <set>
#include <map>
#include <queue>
#include <list>
/* -- */
#include "common/Errors.hpp"
/* -- */
#include "trace/values/Values.hpp"
#include "trace/EntityTypes.hpp"
#include "trace/Entitys.hpp"
#include "trace/Trace.hpp"
/* -- */
#include "parser/Parser.hpp"
/* -- */
#include "parser/PajeParser/mt_PajeFileManager.hpp"
#include "parser/PajeParser/PajeDefinition.hpp"
#include "parser/PajeParser/ParserDefinitionPaje.hpp"
#include "parser/PajeParser/mt_ParserEventPaje.hpp"
#include "parser/PajeParser/mt_ParserPaje.hpp"
#include "parser/PajeParser/BuilderThread.hpp"
#include "trace/TraceBuilderThread.hpp"
/* -- */
/*
#ifndef WIN32
#include "sys/time.h"
#else
#include <windows.h>

LARGE_INTEGER GetFrequency()
{
  LARGE_INTEGER freq;
  QueryPerformanceFrequency(&freq);
  return freq;
}

#endif



//function for getting a quite accurate timestamp (used for debug purposes and performance evaluation)
unsigned long long  getCurrentTime (){
#ifndef WIN32
        // Get current time in microseconds
        struct timeval ts;
        gettimeofday(&ts,NULL);
        unsigned long long  timeValue = 0;
        timeValue = (1000000 * (unsigned long long)ts.tv_sec) + (unsigned long long)ts.tv_usec;
        //unsigned long long  timeValue = (100000000000000000);
        //double t2=ts.tv_sec+(ts.tv_usec/1000000.0);
        //printf("Current Time: %llu µs \n", timeValue);
        return timeValue;
#else
        LARGE_INTEGER tick, freq;
    freq = GetFrequency();
    QueryPerformanceCounter(&tick);
    return tick.QuadPart / freq.QuadPart;
#endif

}
*/
using namespace std;

mt_ParserPaje::mt_ParserPaje() :
    _ParserDefinition(new ParserDefinitionPaje()),
    _ParserEvent(new mt_ParserEventPaje(_ParserDefinition)),
    _file(NULL),
    _being_processed(false) {
}

mt_ParserPaje::mt_ParserPaje(const string &filename) :
    Parser(filename),
    _ParserDefinition(new ParserDefinitionPaje()),
    _ParserEvent(new mt_ParserEventPaje(_ParserDefinition)),
    _file(NULL),
    _being_processed(false) {
}

mt_ParserPaje::~mt_ParserPaje() {
    delete _ParserDefinition;
    delete _ParserEvent;
    if (_file != NULL)
        delete _file;
}

void mt_ParserPaje::parse(Trace &trace,
                          bool finish_trace_after_parse) {
    // Make sure noone is currently parsing
    //
    // While parsing a file, we may find a createContainer event that requires
    // to parse another file. The _being_processed trick fixes the reentrance
    // problem that may arise.
    if (_being_processed) {
        return;
    }
    _being_processed = true;

    _ParserEvent->setTrace(&trace);

    QMutex mutex;
    QMutex mutex2;
    QWaitCondition ended;
    QWaitCondition trace_ended;
    QSemaphore freeSlots(5);
    QSemaphore linesProduced(5);

    BuilderThread _bt(_ParserEvent, &trace, &ended, &trace_ended,
                      &freeSlots, &linesProduced, &mutex, &mutex2,
                      this);
    QThread consumerThread;

    connect(this, &mt_ParserPaje::produced,
            &_bt, &BuilderThread::run);
    connect(this, &mt_ParserPaje::build_finish,
            &_bt, &BuilderThread::finish_build);
    _bt.moveToThread(&consumerThread);
    // assign it to a thread
    consumerThread.start();

    TraceBuilderThread _T_bt(&trace_ended, &freeSlots, &mutex2);
    QThread traceBuilderThread;
    // this object will handle all trace operations
    connect(&_bt, &BuilderThread::build_trace,
            &_T_bt, &TraceBuilderThread::build_trace);
    connect(&_bt, &BuilderThread::build_finished,
            &_T_bt, &TraceBuilderThread::build_finished);
    _T_bt.moveToThread(&traceBuilderThread);
    // assign it to a thread
    traceBuilderThread.start();

    // process files once at a time
    std::string filename = get_next_file_to_parse();
    while (filename != "") {

        // Open the trace
        try {
            _file = new mt_PajeFileManager(filename.c_str());
        } catch (const char *) {
            delete _file;
            _file = NULL;
            _is_canceled = true;
            finish();
            trace.finish();
            std::cerr << "Cannot open file " << filename.c_str() << std::endl;
            Error::set(Error::VITE_ERR_OPEN, 0, Error::VITE_ERRCODE_WARNING);
            abort();
            return;
        }

        // we use blocks of 10000 lines
        PajeLine *line = (PajeLine *)calloc(10000, sizeof(PajeLine));
        unsigned int i = 0;
        while ((!(_file->eof())) && !(_is_canceled)) {
            try {
#ifdef DBG_PARSER_PAJE
                if ((lineid + 1) == _file->get_line(&line[i])) {
                    _file->print_line();
                    lineid++;
                }
#else
                _file->get_line(&line[i]);
#endif
            } catch (char const *) {
                Error::set(Error::VITE_ERR_EXPECT_ID_DEF, 0, Error::VITE_ERRCODE_ERROR);
                continue;
            }

            // If it's an empty line
            if (line[i]._nbtks == 0) {
                continue;
            }
            // The line starts by a '%' : it's a definition
            else if (line[i]._tokens[0][0] == '%') {
                _ParserDefinition->store_definition(&line[i]);
            }
            // It's an event
            else {
                i++;
                if (i == 10000) { // we finished to build a block, send it to the BuilderThread

                    linesProduced.acquire();
                    Q_EMIT produced(i, line);
                    i = 0;
                    line = (PajeLine *)calloc(10000, sizeof(PajeLine));

                    // if a chunk of file is finished, wait for its complete treatment before releasing the chunk
                    //  avoids reading too quickly in the file
                    if (_file->eoc()) {
                        linesProduced.acquire(5);
                        _file->close_old_chunk();
                        linesProduced.release(5);
                    }
                }
            }
        }
        // send the last batch
        Q_EMIT produced(i, line);
        line = NULL;

        // send the finish signal to the BuilderThread, which will do the same to the TraceBuilderThread
        // both threads will then be finished and ready to destroy
        // locks the mutex and automatically unlocks it when going out of scope
        QMutexLocker locker(&mutex);
        Q_EMIT build_finish();
        ended.wait(&mutex);
        locker.unlock();

        filename = get_next_file_to_parse();
    }

    traceBuilderThread.quit();
    traceBuilderThread.wait();
    consumerThread.quit();
    consumerThread.wait();
    if (finish_trace_after_parse) {
        trace.finish();
        finish();
    }
    _being_processed = false;
}

float mt_ParserPaje::get_percent_loaded() const {
    if (_file != NULL)
        return _file->get_percent_loaded();
    else
        return 0.;
}

#include "moc_mt_ParserPaje.cpp"
