/*****************************************************************************
* smsub Server Manager Subprocess
* Copyright (C) 2020-2024 Syping
*
* Redistribution and use in source and binary forms, with or without modification,
* are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* This software is provided as-is, no warranties are given to you, we are not
* responsible for anything with use of the software, you are self responsible.
*****************************************************************************/

#include <QCoreApplication>
#include <QTextStream>
#include <QDateTime>
#include "SMSubProcess.h"
#include "smsub.h"

SMSubProcess::SMSubProcess(const QString &executable, const QStringList &arguments, const QString &workingDirectory, const int termTimeout, const bool buffReads, const bool keepAlive) :
    termTimeout(termTimeout), buffReads(buffReads), keepAlive(keepAlive)
{
    // Set process executable, arguments and working directory
    process.setProgram(executable);
    process.setArguments(arguments);
    process.setWorkingDirectory(workingDirectory);

    // manage input channel
    process.setInputChannelMode(QProcess::ManagedInputChannel);

    // stdout and stderr in same I/O stream
    process.setProcessChannelMode(QProcess::MergedChannels);

    // Connect process signal handlers
    QObject::connect(&process, &QProcess::readyRead, this, &SMSubProcess::readyRead);
    QObject::connect(&process, &QProcess::errorOccurred, this, &SMSubProcess::processError);
    QObject::connect(&process, &QProcess::started, this, [=]() {
        QTextStream(stderr) << "Subprocess started!" << smsub_endl;
        emit statusUpdated(true, QDateTime::currentDateTimeUtc().toSecsSinceEpoch());
    });
    QObject::connect(&process, QOverload<int, QProcess::ExitStatus>::of(&QProcess::finished), this, [=](int exitCode) {
        QTextStream(stderr) << "Subprocess exited!" << smsub_endl;
        // Exit with the same exit code as the process
        emit statusUpdated(false, QDateTime::currentDateTimeUtc().toSecsSinceEpoch());
        if (!keepAlive)
            QCoreApplication::exit(exitCode);
    });
}

void SMSubProcess::readyRead()
{
#ifdef SMSUB_IODEBUG
    QTextStream(stderr) << "Subprocess I/O RR!" << smsub_endl;
#endif
    // Read process output and emit event
    if (buffReads) {
        while (process.bytesAvailable()) {
#ifdef SMSUB_IODEBUG
            QTextStream(stderr) << "Subprocess I/O W!" << smsub_endl;
#endif
            const QByteArray readData = process.read(1024);
            if (!readData.isEmpty())
                emit outputWritten(readData);
        }
    }
    else {
        while (process.canReadLine()) {
#ifdef SMSUB_IODEBUG
            QTextStream(stderr) << "Subprocess I/O WL!" << smsub_endl;
#endif
            const QByteArray readData = process.readLine().trimmed();
            emit outputWritten(readData + '\n');
        }
    }
}

void SMSubProcess::processError(QProcess::ProcessError error)
{
    // Handle process errors
    if (Q_LIKELY(error == QProcess::FailedToStart)) {
        QTextStream(stderr) << "Process failed to start!" << smsub_endl;
        if (!keepAlive)
            QCoreApplication::exit(1);
    }
    else if (error == QProcess::UnknownError) {
        QTextStream(stderr) << "Unknown error occurred!" << smsub_endl;
        if (!keepAlive)
            QCoreApplication::exit(1);
    }
}

void SMSubProcess::aboutToQuit()
{
    // Main process terminated, terminate subprocess with set timeout
    if (process.state() == QProcess::Running) {
        process.terminate();
        if (!process.waitForFinished(termTimeout)) {
            QTextStream(stderr) << "Failed to terminate process!" << smsub_endl;
        }
    }
}

void SMSubProcess::startProcess()
{
    // Start process as requested
    if (process.state() == QProcess::NotRunning)
        process.start(QIODevice::ReadWrite);
}

void SMSubProcess::stopProcess()
{
    // Terminate process as requested
    if (process.state() == QProcess::Running)
        process.terminate();
}

void SMSubProcess::killProcess()
{
    // Kill process as requested
    if (process.state() == QProcess::Running)
        process.kill();
}

void SMSubProcess::writeInput(const QByteArray &input)
{
#ifdef SMSUB_IODEBUG
    QTextStream(stderr) << "Subprocess I/O IN!" << smsub_endl;
#endif
    // Write input from Unix/IPC socket to process
    if (process.state() == QProcess::Running)
        process.write(input);
}