/*
 * Copyright (C) 2006  Justin Karneges
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this library.  If not, see <https://www.gnu.org/licenses/>.
 *
 */

#include "processquit.h"

#ifndef NO_IRISNET
#include "irisnetglobal_p.h"
#endif

#ifdef QT_GUI_LIB
#include <QApplication>
#endif
#ifdef Q_OS_UNIX
#include <signal.h>
#include <unistd.h>
#endif
#ifdef Q_OS_WIN
#include <windows.h>
#endif

namespace {
// safeobj stuff, from qca
void releaseAndDeleteLater(QObject *owner, QObject *obj)
{
    obj->disconnect(owner);
    obj->setParent(nullptr);
    obj->deleteLater();
}

class SafeSocketNotifier : public QObject {
    Q_OBJECT
public:
    SafeSocketNotifier(int socket, QSocketNotifier::Type type, QObject *parent = nullptr) : QObject(parent)
    {
        sn = new QSocketNotifier(socket, type, this);
        connect(sn, SIGNAL(activated(int)), SIGNAL(activated(int)));
    }

    ~SafeSocketNotifier()
    {
        sn->setEnabled(false);
        releaseAndDeleteLater(this, sn);
    }

    bool                  isEnabled() const { return sn->isEnabled(); }
    int                   socket() const { return int(sn->socket()); }
    QSocketNotifier::Type type() const { return sn->type(); }

public slots:
    void setEnabled(bool enable) { sn->setEnabled(enable); }

signals:
    void activated(int socket);

private:
    QSocketNotifier *sn;
};
} // namespace

#ifndef NO_IRISNET
namespace XMPP {
#endif

Q_GLOBAL_STATIC(QMutex, pq_mutex)
static ProcessQuit *g_pq = nullptr;

inline bool is_gui_app()
{
#ifdef QT_GUI_LIB
    return qobject_cast<QGuiApplication *>(QCoreApplication::instance());
#else
    return false;
#endif
}

class ProcessQuit::Private : public QObject {
    Q_OBJECT
public:
    ProcessQuit *q;

    bool done;
#ifdef Q_OS_WIN
    bool use_handler;
#endif
#ifdef Q_OS_UNIX
    int                 sig_pipe[2];
    SafeSocketNotifier *sig_notifier;
#endif

    Private(ProcessQuit *_q) : QObject(_q), q(_q)
    {
        done = false;
#ifdef Q_OS_WIN
        use_handler = !is_gui_app();
        if (use_handler)
            SetConsoleCtrlHandler((PHANDLER_ROUTINE)winHandler, TRUE);
#endif
#ifdef Q_OS_UNIX
        if (pipe(sig_pipe) == -1) {
            // no support then
            return;
        }

        sig_notifier = new SafeSocketNotifier(sig_pipe[0], QSocketNotifier::Read, this);
        connect(sig_notifier, SIGNAL(activated(int)), SLOT(sig_activated(int)));
        unixWatchAdd(SIGINT);
        unixWatchAdd(SIGHUP);
        unixWatchAdd(SIGTERM);
#endif
    }

    ~Private()
    {
#ifdef Q_OS_WIN
        if (use_handler)
            SetConsoleCtrlHandler((PHANDLER_ROUTINE)winHandler, FALSE);
#endif
#ifdef Q_OS_UNIX
        unixWatchRemove(SIGINT);
        unixWatchRemove(SIGHUP);
        unixWatchRemove(SIGTERM);
        delete sig_notifier;
        close(sig_pipe[0]);
        close(sig_pipe[1]);
#endif
    }

#ifdef Q_OS_WIN
    static BOOL winHandler(DWORD ctrlType)
    {
        Q_UNUSED(ctrlType);
        QMetaObject::invokeMethod(g_pq->d, "ctrl_ready", Qt::QueuedConnection);
        return TRUE;
    }
#endif

#ifdef Q_OS_UNIX
    static void unixHandler(int sig)
    {
        Q_UNUSED(sig);
        unsigned char c = 0;
        if (::write(g_pq->d->sig_pipe[1], &c, 1) == -1) {
            // TODO: error handling?
            return;
        }
    }

    void unixWatchAdd(int sig)
    {
        struct sigaction sa;
        sigaction(sig, nullptr, &sa);
        // if the signal is ignored, don't take it over.  this is
        //   recommended by the glibc manual
        if (sa.sa_handler == SIG_IGN)
            return;
        sigemptyset(&(sa.sa_mask));
        sa.sa_flags   = 0;
        sa.sa_handler = unixHandler;
        sigaction(sig, &sa, nullptr);
    }

    void unixWatchRemove(int sig)
    {
        struct sigaction sa;
        sigaction(sig, nullptr, &sa);
        // ignored means we skipped it earlier, so we should
        //   skip it again
        if (sa.sa_handler == SIG_IGN)
            return;
        sigemptyset(&(sa.sa_mask));
        sa.sa_flags   = 0;
        sa.sa_handler = SIG_DFL;
        sigaction(sig, &sa, nullptr);
    }
#endif

public slots:
    void ctrl_ready()
    {
#ifdef Q_OS_WIN
        do_emit();
#endif
    }

    void sig_activated(int)
    {
#ifdef Q_OS_UNIX
        unsigned char c;
        if (::read(sig_pipe[0], &c, 1) == -1) {
            // TODO: error handling?
            return;
        }

        do_emit();
#endif
    }

private:
    void do_emit()
    {
        // only signal once
        if (!done) {
            done = true;
            emit q->quit();
        }
    }
};

ProcessQuit::ProcessQuit(QObject *parent) : QObject(parent) { d = new Private(this); }

ProcessQuit::~ProcessQuit() { delete d; }

ProcessQuit *ProcessQuit::instance()
{
    QMutexLocker locker(pq_mutex());
    if (!g_pq) {
        g_pq = new ProcessQuit;
        g_pq->moveToThread(QCoreApplication::instance()->thread());
#ifndef NO_IRISNET
        irisNetAddPostRoutine(cleanup);
#endif
    }
    return g_pq;
}

void ProcessQuit::reset()
{
    QMutexLocker locker(pq_mutex());
    if (g_pq)
        g_pq->d->done = false;
}

void ProcessQuit::cleanup()
{
    delete g_pq;
    g_pq = nullptr;
}

#ifndef NO_IRISNET
}
#endif // NO_IRISNET

#include "processquit.moc"
