/****************************************************************************
**
** This file is part of the KD Soap library.
**
** SPDX-FileCopyrightText: 2010-2021 Klarälvdalens Datakonsult AB, a KDAB Group company <info@kdab.com>
**
** SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDAB-KDSoap OR LicenseRef-KDAB-KDSoap-US
**
** Licensees holding valid commercial KD Soap licenses may use this file in
** accordance with the KD Soap Commercial License Agreement provided with
** the Software.
**
** Contact info@kdab.com if any conditions of this licensing are not clear to you.
**
****************************************************************************/

#include "KDSoapClientThread_p.h"
#include <QDebug>
#include "KDSoapPendingCallWatcher.h"
#include "KDSoapClientInterface.h"
#include "KDSoapClientInterface_p.h"
#include "KDSoapPendingCall.h"
#include "KDSoapPendingCall_p.h"
#include <QNetworkRequest>
#include <QNetworkProxy>
#include <QBuffer>
#include <QEventLoop>
#include <QAuthenticator>

KDSoapClientThread::KDSoapClientThread(QObject *parent)
    : QThread(parent)
    , m_stopThread(false)
{
}

// Called by the main thread
void KDSoapClientThread::enqueue(KDSoapThreadTaskData *taskData)
{
    // On hindsight, it would have been simpler to use signal/slots
    // to communicate with secondary thread (turning this class
    // into a simple QObject with a QThread internally)

    QMutexLocker locker(&m_mutex);
    m_queue.append(taskData);
    m_queueNotEmpty.wakeOne();
}

void KDSoapClientThread::run()
{
    QNetworkAccessManager accessManager;
    // Use own QEventLoop so its slot quit() is executed in this thread
    // (using QThread::exec/quit would try to call QThread::quit() in main thread,
    //  which is blocked on semaphore)
    QEventLoop eventLoop;

    while (true) {
        QMutexLocker locker(&m_mutex);
        while (!m_stopThread && m_queue.isEmpty()) {
            m_queueNotEmpty.wait(&m_mutex);
        }
        if (m_stopThread) {
            break;
        }
        KDSoapThreadTaskData *taskData = m_queue.dequeue();
        locker.unlock();

        KDSoapThreadTask task(taskData); // must be created here, so that it's in the right thread
        connect(&task, &KDSoapThreadTask::taskDone, &eventLoop, &QEventLoop::quit);
        connect(&accessManager, &QNetworkAccessManager::authenticationRequired, &task, &KDSoapThreadTask::slotAuthenticationRequired);
        task.process(accessManager);

        // Process events until the task tells us the handling of that task is finished
        eventLoop.exec();
    }
}

void KDSoapThreadTask::process(QNetworkAccessManager &accessManager)
{
    // Can't use m_iface->asyncCall, it would use the accessmanager from the main thread
    // KDSoapPendingCall pendingCall = m_iface->asyncCall(m_method, m_message, m_action);

    // Headers should be always qualified
    for (KDSoapMessage &header : m_data->m_headers) {
        header.setQualified(true);
    }

    QNetworkCookieJar *jar = m_data->m_iface->d->accessManager()->cookieJar();
    accessManager.setCookieJar(jar);

    accessManager.setProxy(m_data->m_iface->d->accessManager()->proxy());

    QBuffer *buffer = m_data->m_iface->d->prepareRequestBuffer(m_data->m_method,
                                                               m_data->m_message,
                                                               m_data->m_action,
                                                               m_data->m_headers);
    QNetworkRequest request = m_data->m_iface->d->prepareRequest(m_data->m_method, m_data->m_action);
    QNetworkReply *reply = accessManager.post(request, buffer);
    m_data->m_iface->d->setupReply(reply);
    KDSoapPendingCall pendingCall(reply, buffer);
    pendingCall.d->soapVersion = m_data->m_iface->d->m_version;

    KDSoapPendingCallWatcher *watcher = new KDSoapPendingCallWatcher(pendingCall, this);
    connect(watcher, &KDSoapPendingCallWatcher::finished, this, &KDSoapThreadTask::slotFinished);
}

void KDSoapThreadTask::slotFinished(KDSoapPendingCallWatcher *watcher)
{
    m_data->m_response = watcher->returnMessage();
    m_data->m_responseHeaders = watcher->returnHeaders();
    m_data->m_semaphore.release();
    // Helgrind bug: says this races with main thread. Looks like it's confused by QSharedDataPointer
    // qDebug() << m_data->m_returnArguments.value();
    watcher->deleteLater();

    emit taskDone();
}

void KDSoapClientThread::stop()
{
    QMutexLocker locker(&m_mutex);
    m_stopThread = true;
    m_queueNotEmpty.wakeAll();
}

void KDSoapThreadTask::slotAuthenticationRequired(QNetworkReply *reply, QAuthenticator *authenticator)
{
    m_data->m_authentication.handleAuthenticationRequired(reply, authenticator);
}
