
#include "R2Task.h"

R2Task::R2Task(const QString &cmd, bool transient)
{
    Q_UNUSED(transient);
    task = r_core_task_new(
        Core()->core(),
#if R2_VERSION_NUMBER >= 60005
        R_CORE_TASK_MODE_THREAD,
#endif
        true,
        cmd.toLocal8Bit().constData(),
        static_cast<RCoreTaskCallback>(&R2Task::taskFinishedCallback),
        this);
    if (task) {
        // Keep ownership on the iaito side. Transient tasks are freed from the
        // worker thread, but radare2's thread launcher still touches the thread
        // lock during shutdown, which can trigger a use-after-free crash.
        task->transient = false;
    }
}

R2Task::~R2Task()
{
    if (!task) {
        return;
    }
    if (!started) {
        r_core_task_free(task);
        task = nullptr;
        return;
    }

    joinTask();
    bool deleted = false;
    if (task->core) {
        deleted = r_core_task_del(&task->core->tasks, task->id);
    }
    if (!deleted) {
        r_core_task_free(task);
    }
    task = nullptr;
}

void R2Task::taskFinishedCallback(void *user, char *res)
{
    reinterpret_cast<R2Task *>(user)->taskFinished(res);
}

void R2Task::taskFinished(char *res)
{
    // Copy the result into resultRaw so it remains valid after r2 frees its
    // internal buffer. Doing this here (in the r2 task callback) ensures we
    // capture the output before it can be destroyed.
    if (res) {
        resultRaw = QByteArray(res);
    } else {
        resultRaw.clear();
    }
    emit finished();
}

void R2Task::startTask()
{
    if (!task) {
        return;
    }
    started = true;
    r_core_task_enqueue(&Core()->core_->tasks, task);
}

void R2Task::breakTask()
{
    if (task && task->core) {
        // Set the console breaked flag so the running r2 command checks it
        // and returns early.  Uses the same mechanism as the analysis
        // progress dialog interrupt (see commit a0ce535).
        Core()->setConsBreaked();
    }
}

void R2Task::joinTask()
{
    if (task) {
        r_core_task_join(&Core()->core_->tasks, nullptr, task->id);
        waitForThread();
    }
}

void R2Task::waitForThread()
{
    if (!task || threadWaited || !task->thread) {
        return;
    }
    r_th_wait(task->thread);
    threadWaited = true;
}

QString R2Task::getResult()
{
    if (!resultRaw.isEmpty()) {
        return QString::fromUtf8(resultRaw.constData());
    }
    if (task == nullptr) {
        return QString("");
    }
    return QString::fromUtf8(task->res ? task->res : "");
}

QJsonDocument R2Task::getResultJson()
{
    if (!resultRaw.isEmpty()) {
        return Core()->parseJson(resultRaw.constData(), task ? task->cmd : nullptr);
    }
    if (task == nullptr) {
        return QJsonDocument();
    }
    return Core()->parseJson(task->res, task->cmd);
}

const char *R2Task::getResultRaw()
{
    if (!resultRaw.isEmpty()) {
        return resultRaw.constData();
    }
    return task != nullptr ? task->res : nullptr;
}
