/*
** This file is part of the ViTE project.
**
** This software is governed by the CeCILL-A license under French law
** and abiding by the rules of distribution of free software. You can
** use, modify and/or redistribute the software under the terms of the
** CeCILL-A license as circulated by CEA, CNRS and INRIA at the following
** URL: "http://www.cecill.info".
**
** As a counterpart to the access to the source code and rights to copy,
** modify and redistribute granted by the license, users are provided
** only with a limited warranty and the software's author, the holder of
** the economic rights, and the successive licensors have only limited
** liability.
**
** In this respect, the user's attention is drawn to the risks associated
** with loading, using, modifying and/or developing or reproducing the
** software by the user in light of its specific status of free software,
** that may mean that it is complicated to manipulate, and that also
** therefore means that it is reserved for developers and experienced
** professionals having in-depth computer knowledge. Users are therefore
** encouraged to load and test the software's suitability as regards
** their requirements in conditions enabling the security of their
** systems and/or data to be ensured and, more generally, to use and
** operate it in the same conditions as regards security.
**
** The fact that you are presently reading this means that you have had
** knowledge of the CeCILL-A license and that you accept its terms.
**
**
** ViTE developers are (for version 0.* to 1.0):
**
**        - COULOMB Kevin
**        - FAVERGE Mathieu
**        - JAZEIX Johnny
**        - LAGRASSE Olivier
**        - MARCOUEILLE Jule
**        - NOISETTE Pascal
**        - REDONDY Arthur
**        - VUCHENER Clément
**
*/
#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;

    _bt.connect((const QObject*)this,
                SIGNAL(produced(int, PajeLine*)),
                SLOT(run(int, PajeLine*)));
    _bt.connect((const QObject*)this,
                SIGNAL(build_finish()),
                SLOT(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
    _T_bt.connect((const QObject*)&_bt,
                  SIGNAL(build_trace(int, Trace_builder_struct*)),
                  SLOT(build_trace(int, Trace_builder_struct*)));
    _T_bt.connect((const QObject*)&_bt,
                  SIGNAL(build_finished()),
                  SLOT(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));
        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();
                    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
        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);
        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.;
}
