/**
 *
 * @file src/trace/SerializerDispatcher.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 Thibault Soucarre
 *
 * @date 2024-07-17
 */
#include "trace/SerializerDispatcher.hpp"
#include "trace/SerializerWriter.hpp"
#include "trace/IntervalOfContainer.hpp"
#include <boost/thread/thread.hpp>
#include <QObject>
#include <QThread>
#include <QWaitCondition>
#include <QMutex>
#include <QSemaphore>

SerializerDispatcher::SerializerDispatcher() {
    // we launch all threads just now

    _nb_threads = boost::thread::hardware_concurrency();
    // SerializerWriter* evt=NULL;

    launch_threads();

    // evt=&(evt_array[_uid%nb_cpus]);//&(SerializerWriter::Instance());
    /*_cond = evt->get_cond();
            _freeSlots= evt->get_sem_free();
            _itcProduced= evt->get_sem_produced();
            _thread= evt->get_thread();*/
    // printf("récupérons %s %d %p %p\n",_name.to_string().c_str(),_uid%boost::thread::hardware_concurrency(), _thread, _cond);
}
SerializerDispatcher::~SerializerDispatcher() { }

void SerializerDispatcher::launch_threads() {

    _mutex = new QMutex();
    _killed = false;
    _previous_call = 0;
    _evt_array = new SerializerWriter[_nb_threads];
    for (unsigned int i = 0; i < _nb_threads; i++) {

        QWaitCondition *_cond = new QWaitCondition();
        QThread *_thread = new QThread();

        QSemaphore *_freeSlots = new QSemaphore(1);
        QSemaphore *_itcProduced = new QSemaphore(1);

        // TODO : try to add dynamic signal/slots creation and use to avoid constant rebinding
        if (i == 0) {
            /*_evt_array[i].connect((const QObject*)this,
                 SIGNAL(build_finish()),
                 SLOT(finish_build()));*/

            _evt_array[i].connect((const QObject *)this,
                                  SIGNAL(dump_on_disk(IntervalOfContainer *, char *)),
                                  SLOT(dump_on_disk(IntervalOfContainer *, char *)));

            _evt_array[i].connect((const QObject *)this,
                                  SIGNAL(load_data(IntervalOfContainer *, char *)),
                                  SLOT(load(IntervalOfContainer *, char *)));
        }

        new (&_evt_array[i]) SerializerWriter(_cond, _freeSlots, _itcProduced, _thread, _mutex);
        // _evt_array[i].set_values(_cond, _freeSlots, _itcProduced, _thread);
        _evt_array[i].moveToThread(_thread);
        _thread->start();
    }
}
void SerializerDispatcher::kill_all_threads() {
    if (!_killed) {
        for (unsigned int i = 0; i < _nb_threads; i++) {

            if (_evt_array[i].get_thread() != NULL && _evt_array[i].get_thread()->isRunning()) {

                _evt_array[i].connect((const QObject *)this,
                                      SIGNAL(build_finish()),
                                      SLOT(finish_build()));
                // locks the mutex and automatically unlocks it when going out of scope
                QMutexLocker locker(_mutex);
                Q_EMIT build_finish();
                QWaitCondition *_cond = _evt_array[i].get_cond();
                _cond->wait(_mutex);
                locker.unlock();
                _evt_array[i].get_thread()->quit();
            }
        }
        // delete _evt_array;
        _killed = true;
    }
}

void SerializerDispatcher::dump(IntervalOfContainer *itc, char *name) {
    bool changed = false;
    SerializerWriter *evt = &(_evt_array[_previous_call]);
    QSemaphore *_itcProduced = evt->get_sem_produced();
    while (_itcProduced->tryAcquire(1, 10) == false) {
        // the connected working thread is not free
        changed = true;
        _previous_call++;
        _previous_call = _previous_call % _nb_threads;
        _itcProduced = _evt_array[_previous_call].get_sem_produced();
    }

    if (changed != false) {
        // the acquired semaphore isn't the same as the initial one, disconnect the signl and connect to the right thread

        this->disconnect(SIGNAL(dump_on_disk(IntervalOfContainer *, char *)));
        _evt_array[_previous_call].connect((const QObject *)this,
                                           SIGNAL(dump_on_disk(IntervalOfContainer *, char *)),
                                           SLOT(dump_on_disk(IntervalOfContainer *, char *)));
        //  printf("changing at the end %s %d\n",_name.to_string().c_str(),slot_number);
        //_thread=evt_array[slot_number].get_thread();
        // _cond=evt_array[slot_number].get_cond();
    }
    itc->_loaded = false;
    Q_EMIT dump_on_disk(itc, name);
    _evt_array[_previous_call].get_sem_free()->release();
}

void SerializerDispatcher::load(IntervalOfContainer *itc, char *name) {

    bool changed = false;
    SerializerWriter *evt = &(_evt_array[_previous_call]);
    QSemaphore *_itcProduced = evt->get_sem_produced();
    while (_itcProduced->tryAcquire(1, 10) == false) {
        // the connected working thread is not free
        changed = true;
        _previous_call++;
        _previous_call = _previous_call % _nb_threads;

        _itcProduced = _evt_array[_previous_call].get_sem_produced();
    }

    if (changed != false) {
        // the acquired semaphore isn't the same as the initial one, disconnect the signl and connect to the right thread

        this->disconnect(SIGNAL(load_data(IntervalOfContainer *, char *)));
        _evt_array[_previous_call].connect((const QObject *)this,
                                           SIGNAL(load_data(IntervalOfContainer *, char *)),
                                           SLOT(load(IntervalOfContainer *, char *)));
        //_thread=evt_array[slot_number].get_thread();
        // _cond=evt_array[slot_number].get_cond();
    }
    itc->_loaded = true;
    Q_EMIT load_data(itc, name);
    _evt_array[_previous_call].get_sem_free()->release();
}

void SerializerDispatcher::init() {
    _killed = false;
    launch_threads();
}

#include "moc_SerializerDispatcher.cpp"
