void dtkComposerGraphView::update(void)
{
    if (!d->graphviz_avail)
        return;

    QByteArray content = d->graph->toString().append("\n").toLocal8Bit() ;
    // run dot
    QStringList arglist;
    arglist << "-Tsvg";
    QString command = "dot";
    QProcess cmd;
    QStringList PATH =  QProcessEnvironment::systemEnvironment().value("PATH").split(":") ;
    QDir::setSearchPaths("bin",PATH);
    if(QFile("bin:"+command).exists()) {

        dtkTrace() << "run graphviz dot" ;
        cmd.start(command, arglist, QProcess::Unbuffered | QProcess::ReadWrite);
        cmd.write(content);
        cmd.closeWriteChannel();
        cmd.waitForBytesWritten();
        qlonglong timeout = 3000;
        QString stdout_data;
        if (cmd.waitForFinished(timeout)) {
            QByteArray svg = cmd.readAllStandardOutput();
            this->load(svg);
        } else {
            dtkWarn() << "graphviz timeout !";
        }
    } else {
        d->graphviz_avail = false;
        dtkWarn() << "can't find 'dot' binary in PATH, graphviz probably not installed";
    }
}
void medMessageController::showInfo(const QString& text,unsigned int timeout)
{
    if ( dynamic_cast<QApplication *>(QCoreApplication::instance()) ) 
    {
        // GUI
        medMessageInfo *message = new medMessageInfo(
                text,0,timeout);
        emit addMessage(message);
    } else {
        dtkTrace() << text;
    }
}
void dtkComposerNodeCommunicatorSend::run(void)
{
    d->emitter.setData(false);
    if (!d->receiver_data.isEmpty()  && !d->receiver_target.isEmpty() ) {

        if (d->receiver_comm.isEmpty() && d->receiver_socket.isEmpty()) {
            dtkError() << "Send node at either a socket or a communicator.";
            return;
        }

        QVariant v = d->receiver_data.variant();

        qint32 tag = 0;
        if (!d->receiver_tag.isEmpty())
            tag = d->receiver_tag.data();

        qlonglong target = d->receiver_target.data();

        if (d->receiver_comm.isEmpty()) {
            d->socket = d->receiver_socket.constData();
            d->communicator = NULL;
            //FIXME: we need the jobid
            QString jobid;
            dtkDistributedMessage *msg = new dtkDistributedMessage(dtkDistributedMessage::DATA, jobid, target, v);
            msg->addHeader("Tag",QString::number(tag));
            msg->send(d->socket);
        } else {
            //FIXME:[migration] new transmitter requieres a clone method in object, even if dataTransmission is set to reference
            d->communicator = d->receiver_comm.constData();
            d->socket = NULL;
            // FIXME: [migration] handle variant in send (should be ok with new distributed layer
            // d->communicator->send(v, target , tag);
        }


        d->emitter.setData(true);

        dtkTrace() << "send to target: " << target ;

    } else {
        dtkWarn() << "Inputs not specified in Send node. Nothing is done"  ;
    }
}
void dtkComposerNodeCommunicatorReceive::run(void)
{

    if (!d->receiver_source.isEmpty()) {


        if (d->receiver_comm.isEmpty() && d->receiver_socket.isEmpty()) {
            dtkError() << "Send node at either a socket or a communicator.";
            return;
        }

        d->source = d->receiver_source.data();

        d->tag = 0;
        if (!d->receiver_tag.isEmpty())
            d->tag = d->receiver_tag.data();

        d->emitter.clearData();
        if (!d->receiver_data.isEmpty()) {
            d->emitter.setData(d->receiver_data.variant());
        }

        if (d->receiver_comm.isEmpty()) {
            QTcpSocket *socket = d->receiver_socket.constData();
            dtkDebug() << "TCP communicator. Parse message from socket, waiting for tag" << d->tag;
            if (d->msg_map.contains(d->tag)) {
                dtkDebug() << "msg already received for tag" << d->tag;
                // d->emitter.setTwinned(false);
                dtkDistributedMessage *msg = d->msg_map.take(d->tag);
                d->emitter.setData(msg->content());
                // d->emitter.setTwinned(true);
                delete msg;
                return;
            } else {
                dtkTrace() << "msg not yet received, wait for data";
            }

            socket->blockSignals(true); // needed ?

            if (!socket->waitForReadyRead(300000)) {
                dtkWarn() << "Data not ready in receive for rank " << d->source;
            } else {
                dtkDistributedMessage msg;
                msg.parse(socket);
                qlonglong msg_tag = msg.header("Tag").toLongLong();
                if (msg_tag == d->tag || d->tag == dtkDistributedCommunicator::ANY_TAG) {
                    dtkTrace() << "OK, this is the expected tag " << d->tag;
                    // d->emitter.setTwinned(false);
                    d->emitter.setData(msg.content());
                    // d->emitter.setTwinned(true);
                    if (d->tag == dtkDistributedCommunicator::ANY_TAG)
                        d->tag = msg_tag;
                } else {
                    //store msg for another call with the right tag
                    dtkInfo() << "Msg received, but wrong tag, store the msg" << d->tag << msg_tag;
                    d->msg_map.insert(msg_tag, &msg);
                    this->run(); // do it again
                }
            }
            socket->blockSignals(false); // needed ?
        } else { // MPI
            QByteArray array;
            dtkDistributedCommunicatorStatus status;
            dtkDistributedCommunicator *communicator = d->receiver_comm.constData();
            //FIXME:[migration] new transmitter requieres a clone method in object, even if dataTransmission is set to reference
            communicator->receive(array, d->source, d->tag, status);

            if (d->tag == dtkDistributedCommunicator::ANY_TAG)
                d->tag = status.tag();
            if (d->source == dtkDistributedCommunicator::ANY_SOURCE)
                d->source = status.source();

            if (!array.isEmpty()) {
                // d->emitter.setTwinned(false);
                d->emitter.setData(array);
                // d->emitter.setTwinned(true);
            } else {
                dtkWarn() << "Empty data in receive";
                d->emitter.clearData();
            }
        }
    } else {
        d->emitter.clearData();
        dtkWarn() << "Inputs not specified in receive. Nothing is done";
        d->tag = 0;
        d->source = 0;
    }

    d->emitter_source.setData(d->source);
    d->emitter_tag.setData(d->tag);

}
void dtkComposerNodeSpawn::begin(void)
{
    if (!d->communicator) {
        // we are running on the controller, for the first time:
        // need to spawn processes
        if (d->size_receiver.isEmpty()) {
            d->np =  QThread::idealThreadCount();
            dtkInfo() << "Set number of spawned proceses to" << d->np;
        } else {
            d->np = d->size_receiver.data();
        }
        d->policy.setNWorkers(d->np);
        // FIXME: don't use hardcoded plugin name
        d->policy.setType("qthreads");
        d->communicator  = d->policy.communicator();
        // d->internal_comm = d->manager.spawn();
        d->internal_comm_emitter.setData(d->internal_comm);
        d->rank = d->internal_comm->rank();
        d->rank_emitter.setData(d->rank);

        if (!d->internal_comm) {
            dtkError() << "NULL internal communicator, spawn has failed !";
            return;
        }

        dtkComposerEvaluatorProcess p;
        p.setInternalCommunicator(d->internal_comm);
        // p.setParentCommunicator(comm);
        // p.setFactory(factory);

        //FIXME: don't use manager
        // d->manager.exec(&p);

    } else {
        dtkTrace() << "communicator exists,  no spawn";
    }

    int first_transmitter = 3;
    if (d->is_parent) {
        int rank =  dtkDistributedCommunicator::ROOT;

        if (d->current_hash != d->last_sent_hash){
            // send sub-composition to rank 0 on remote node
            QByteArray compo = d->composition.toByteArray();
            dtkDebug() << "running node remote begin statement on controller, send composition of size " << compo.size();
            d->communicator->broadcast(compo, rank);
            d->last_sent_hash=d->current_hash;
        } else {
            dtkDebug() << "composition hash hasn't changed, send 'not-modified' to slave";
            QByteArray data = QString("not-modified").toUtf8();
            d->communicator->broadcast(data,  rank);
        }

        // then send transmitters data
        int max  = dtkComposerNodeComposite::receivers().count();
        for (int i = first_transmitter; i < max; i++) {
            dtkComposerTransmitterReceiverVariant *t = dynamic_cast<dtkComposerTransmitterReceiverVariant *>(dtkComposerNodeComposite::receivers().at(i));
            // FIXME: use our own transmitter variant list (see control nodes)
            QByteArray array;
            QDataStream stream(&array, QIODevice::WriteOnly);
            stream << t->variant();

            dtkDebug() << "sending transmitter" << i << "of size" << array.size();
            d->communicator->broadcast(array, rank);
        }
    } else if (d->communicator) {
        if (d->rank < 0) {
            dtkDebug() << "get rank/size on slave";
            d->internal_comm_emitter.setData(d->internal_comm);
            d->rank = d->internal_comm->rank();
            d->np = d->internal_comm->size();
            dtkDebug() << "rank/size"<< d->rank << d->np;
        }

        dtkTrace() << "get transmitter data";
        // running on the slave, receive data and set transmitters
        int max  = dtkComposerNodeComposite::receivers().count();
        for (int i = first_transmitter; i < max; i++) {
            dtkComposerTransmitterReceiverVariant *t = dynamic_cast<dtkComposerTransmitterReceiverVariant *>(dtkComposerNodeComposite::receivers().at(i));
            QByteArray array;
            qint16 parent_rank = 0;
            d->communicator->broadcast(array, parent_rank);
            // t->setTwinned(false);
            QDataStream stream(&array, QIODevice::ReadOnly);
            QVariant variant;
            stream >> variant;
            // FIXME : how can we set data ?
            // t->setVariant(variant);
            // t->setTwinned(true);
        }
    } else {
void dtkComposerEvaluatorSlave::run(void)
{
    d->status = 0;

    if ( !d->communicator_i) {
        d->communicator_i = dtkDistributed::communicator::instance();
        if (d->communicator_i->rank() == 0) {
            std::cout << QString("DTK_JOBID="+this->jobId()).toStdString() << std::endl << std::flush;
        }
    }

    if (!d->factory) {
        dtkFatal() << "No factory set ! abort slave execution";
        d->status = 1;
        return;
    }

    int rank = d->communicator_i->rank();
    int size = d->communicator_i->size();
    dtkDebug() << "communicator size is" << size;
    dtkDebug() << "our rank is" << rank;
    bool new_composition;



    if ( rank == 0) {

        QScopedPointer<dtkDistributedMessage> msg;

        if (!this->isConnected()) {
            dtkDebug() << "connect to server" << d->server;
            this->connect(d->server);
            if (this->isConnected()) {
                if (!d->composition_socket) {
                    dtkDebug() << "open second socket to server" << d->server;
                    d->composition_socket = new QTcpSocket;
                     d->composition_socket->connectToHost(d->server.host(), d->server.port());
                     if (d->composition_socket->waitForConnected()) {
                         msg.reset(new dtkDistributedMessage(dtkDistributedMessage::SETRANK,this->jobId(), dtkDistributedMessage::SLAVE_RANK ));
                         msg->send(d->composition_socket);
                     } else {
                         dtkError() << "Can't connect to server";
                         d->status = 1;
                         return;
                     }
                }

                dtkDebug() << "connected, send our jobid to server" << this->jobId();
                msg.reset(new dtkDistributedMessage(dtkDistributedMessage::SETRANK,this->jobId(),0));
                msg->send(this->socket());
                this->socket()->flush();
                this->socket()->setParent(0);
            } else  {
                dtkFatal() << "Can't connect to server" << d->server;
                d->status = 1;
                return;
            }
        }

        QString composition;

        dtkDebug() << "Wait for composition from controller " ;

        if (d->composition_socket->bytesAvailable() > 10) {
            dtkInfo() << "data already available, try to parse composition " << d->composition_socket->bytesAvailable();
        } else if (!d->composition_socket->waitForReadyRead(600000)) {
            dtkFatal() << "No data received from server after 10mn, abort " ;
            d->status = 1;
            return;
        } else {
            dtkDebug() << "Ok, data received on composition socket, parse" ;
        }

        msg.reset(new dtkDistributedMessage);
        msg->parse(d->composition_socket);
        if (msg->type() == "xml") {
            new_composition = true;
            composition = QString(msg->content());
            d->last_controller_rank = msg->header("x-forwarded-for").toInt();
            d->composition_cache.insert(d->last_controller_rank, composition);
        } else if (msg->type() == "not-modified") { // reuse the old composition
            if (msg->header("x-forwarded-for").toInt() == d->last_controller_rank) {
                new_composition = false;
            } else {
                d->last_controller_rank = msg->header("x-forwarded-for").toInt();
                dtkDebug() << "not modified, but from another controller" << d->last_controller_rank;
                new_composition = true;
                composition = d->composition_cache.value(d->last_controller_rank);
            }
        } else {
            dtkFatal() << "Bad composition type, abort" << msg->type() << msg->content();
            d->status = 1;
            return;
        }

        if (new_composition && composition.isEmpty()) {
            dtkFatal() << "Empty composition, abort" ;
            d->status = 1;
            return;
        }

        dtkDebug() << "got composition from controller:" << composition;
        if (new_composition) {
            dtkDebug() << "new composition";
            if  (size > 1) {
                dtkDebug() << "send composition to our slaves";
                for (int i=1; i< size; i++) {
                    d->communicator_i->send(composition,i,0);
                }
            }
            dtkDebug() << "parse composition" ;
            d->reader->readString(composition);
        } else {
            dtkInfo() << "composition hasn't changed";
            for (int i=1; i< size; i++)
                d->communicator_i->send(QString("rerun"),i,0);
        }
        if (new_composition) {
            if (dtkComposerNodeRemote *remote = dynamic_cast<dtkComposerNodeRemote *>(d->scene->root()->nodes().first()->wrapee())) {
                //FIXME: can we remove this ?
                // this->communicator()->setProperty("jobid",this->jobId());
                remote->setSlave(this);
                remote->setJob(this->jobId());
                remote->setCommunicator(d->communicator_i);
            } else {
                dtkFatal() <<  "Can't find remote node in composition, abort";
                d->status = 1;
                return;
            }
        }
        dtkDebug() << "run composition" ;
        if (QThread::currentThread() == qApp->thread()) {
            dtkTrace() << "running on main thread, create a thread for the evaluator"  ;
            QThread *workerThread = new QThread(this);
            QObject::connect(workerThread, SIGNAL(started()),  d->evaluator, SLOT(run()), Qt::DirectConnection);
            QObject::connect(d->evaluator, SIGNAL(evaluationStopped()), workerThread, SLOT(quit()));

            QEventLoop loop;
            loop.connect(d->evaluator, SIGNAL(evaluationStopped()), &loop, SLOT(quit()));
            loop.connect(qApp, SIGNAL(aboutToQuit()), &loop, SLOT(quit()));

            this->socket()->moveToThread(workerThread);
            workerThread->start();

            loop.exec();

            workerThread->wait();
            workerThread->deleteLater();
        } else {
            dtkTrace() << "running on dedicated thread,run the evaluator" ;
            d->evaluator->run_static();
        }

        dtkDebug() << "finished" ;

    } else {
        QString composition;
        d->communicator_i->receive(composition,0,0);

        if (composition != "rerun") {
            dtkDebug() << "new/changed composition, read"  ;
            dtkDebug() << " composition is " << composition ;
            d->reader->readString(composition);
            dtkDebug() << "read done" ;
        } else {
            dtkDebug() << "reuse composition" ;
        }

        if (dtkComposerNodeRemote *remote = dynamic_cast<dtkComposerNodeRemote *>(d->scene->root()->nodes().first()->wrapee())) {
            remote->setSlave(this);
            remote->setJob(this->jobId());
            remote->setCommunicator(d->communicator_i);
            dtkDebug() << "run composition" ;

            QThread *workerThread = new QThread(this);
            QObject::connect(workerThread, SIGNAL(started()),  d->evaluator, SLOT(run()), Qt::DirectConnection);
            QObject::connect(d->evaluator, SIGNAL(evaluationStopped()), workerThread, SLOT(quit()));
            QEventLoop loop;
            loop.connect(d->evaluator, SIGNAL(evaluationStopped()), &loop, SLOT(quit()));
            loop.connect(qApp, SIGNAL(aboutToQuit()), &loop, SLOT(quit()));

            workerThread->start();
            loop.exec();

            workerThread->wait();
            workerThread->deleteLater();
            // d->evaluator->run_static();
            dtkDebug() << "finished" ;
        } else {
            dtkFatal() <<  "Can't find remote node in composition, abort";
            d->status = 1;
            return;
        }
    }
}