// Copyright (C) 2015 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause

#include <qstringlist.h>
#include <qlist.h>

#include "qaxfactory.h"

#include <string.h>

#include <qt_windows.h>

QT_BEGIN_NAMESPACE

static DWORD *classRegistration = nullptr;
static DWORD dwThreadID;
static bool qAxActivity = false;
static HANDLE hEventShutdown;

#ifdef QT_DEBUG
static const DWORD dwTimeOut = 1000;
static const DWORD dwPause = 500;
#else
static const DWORD dwTimeOut = 5000; // time for EXE to be idle before shutting down
static const DWORD dwPause = 1000; // time to wait for threads to finish up
#endif

extern HANDLE qAxInstance;
extern bool qAxIsServer;
extern bool qAxOutProcServer;
extern wchar_t qAxModuleFilename[MAX_PATH];
extern QString qAxInit();
extern void qAxCleanup();
extern HRESULT UpdateRegistry(bool bRegister, bool perUser);
extern HRESULT GetClassObject(const GUID &clsid, const GUID &iid, void **ppUnk);
extern ulong qAxLockCount();
extern bool qax_winEventFilter(void *message);

STDAPI DumpIDL(const QString &outfile, const QString &ver);

// Monitors the shutdown event
static DWORD WINAPI MonitorProc(void* /* pv */)
{
    while (true) {
        WaitForSingleObject(hEventShutdown, INFINITE);
        DWORD dwWait=0;
        do {
            qAxActivity = false;
            dwWait = WaitForSingleObject(hEventShutdown, dwTimeOut);
        } while (dwWait == WAIT_OBJECT_0);
        // timed out
        if (!qAxActivity && !qAxLockCount()) // if no activity let's really bail
            break;
    }
    CloseHandle(hEventShutdown);
    PostThreadMessage(dwThreadID, WM_QUIT, 0, 0);
    PostQuitMessage(0);

    return 0;
}

// Starts the monitoring thread
static bool StartMonitor()
{
    dwThreadID = GetCurrentThreadId();
    hEventShutdown = CreateEvent(nullptr, false, false, nullptr);
    if (hEventShutdown == nullptr)
        return false;
    DWORD dwThreadID;
    HANDLE h = CreateThread(nullptr, 0, MonitorProc, nullptr, 0, &dwThreadID);
    return (h != nullptr);
}

void qax_shutDown()
{
    qAxActivity = true;
    if (hEventShutdown)
        SetEvent(hEventShutdown); // tell monitor that we transitioned to zero
}

/*
    Start the COM server (if necessary).
*/
bool qax_startServer(QAxFactory::ServerType type)
{
    if (qAxIsServer)
        return true;

    const QStringList keys = qAxFactory()->featureList();
    const qsizetype keyCount = keys.size();
    if (!keyCount)
        return false;

    if (!qAxFactory()->isService())
        StartMonitor();

    classRegistration = new DWORD[keyCount];
    qsizetype object = 0;
    for (object = 0; object < keyCount; ++object) {
        IUnknown* p = nullptr;
        CLSID clsid = qAxFactory()->classID(keys.at(object));

        // Create a QClassFactory (implemented in qaxserverbase.cpp)
        HRESULT hRes = GetClassObject(clsid, IID_IClassFactory, reinterpret_cast<void **>(&p));
        if (SUCCEEDED(hRes))
            hRes = CoRegisterClassObject(clsid, p, CLSCTX_LOCAL_SERVER,
                type == QAxFactory::MultipleInstances ? REGCLS_MULTIPLEUSE : REGCLS_SINGLEUSE,
                classRegistration+object);
        if (p)
            p->Release();
    }

    qAxIsServer = true;
    return true;
}

/*
    Stop the COM server (if necessary).
*/
bool qax_stopServer()
{
    if (!qAxIsServer || !classRegistration)
        return true;

    qAxIsServer = false;

    const int keyCount = qAxFactory()->featureList().size();
    for (int object = 0; object < keyCount; ++object)
        CoRevokeClassObject(classRegistration[object]);

    delete []classRegistration;
    classRegistration = nullptr;

    Sleep(dwPause); //wait for any threads to finish

    return true;
}

QT_END_NAMESPACE

#if defined(QT_NEEDS_QMAIN)
int qMain(int, char **);
#define main qMain
#else
extern "C" int main(int, char **);
#endif

static inline QStringList commandLineArguments()
{
    QStringList result;
    int size;
    if (wchar_t **argv = CommandLineToArgvW(GetCommandLine(), &size)) {
        result.reserve(size);
        wchar_t **argvEnd = argv + size;
        for (wchar_t **a = argv; a < argvEnd; ++a)
            result.append(QString::fromWCharArray(*a));
        LocalFree(argv);
    }
    return result;
}

static inline bool matchesOption(const QString &arg, const char *option)
{
    return (arg.startsWith(QLatin1Char('/')) || arg.startsWith(QLatin1Char('-')))
        && arg.right(arg.size() - 1).compare(QLatin1String(option), Qt::CaseInsensitive) == 0;
}

namespace {
struct Arg {
    explicit Arg(const QStringList &args)
    {
        argv.reserve(args.size() + 1);
        for (const QString &a : args)
            argv.append(_strdup(a.toLocal8Bit().constData()));
        argv.append(nullptr);
    }

    ~Arg()
    {
        for (int i = 0, last = argv.size() - 1; i < last; ++i)
            free(argv.at(i));
    }

    QList<char *> argv;
};
} // namespace

EXTERN_C int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE /* hPrevInstance */, LPSTR, int /* nShowCmd */)
{
    QT_USE_NAMESPACE

    qAxOutProcServer = true;
    GetModuleFileName(nullptr, qAxModuleFilename, MAX_PATH);
    qAxInstance = hInstance;

    const QStringList cmds = commandLineArguments();
    QStringList unprocessed;
    unprocessed.reserve(cmds.size());

    int nRet = 0;
    bool run = true;
    bool runServer = false;
    for (qsizetype i = 0; i < cmds.size(); ++i) {
        const QString &cmd = cmds.at(i);
        if (matchesOption(cmd, "activex") || matchesOption(cmd, "embedding")) {
            runServer = true;
        } else if (matchesOption(cmd, "unregserver")) {
            nRet = UpdateRegistry(false, false);
            run = false;
            break;
        } else if (matchesOption(cmd, "regserver")) {
            nRet = UpdateRegistry(true, false);
            run = false;
            break;
        } else if (matchesOption(cmd, "unregserverperuser")) {
            nRet = UpdateRegistry(false, true);
            run = false;
            break;
        } else if (matchesOption(cmd, "regserverperuser")) {
            nRet = UpdateRegistry(true, true);
            run = false;
            break;
        } else if (matchesOption(cmd, "dumpidl")) {
            ++i;
            if (i < cmds.size()) {
                const QString &outfile = cmds.at(i);
                ++i;
                QString version;
                if (i < cmds.size() && matchesOption(cmds.at(i), "version")) {
                    ++i;
                    if (i < cmds.size())
                        version = cmds.at(i);
                    else
                        version = QStringLiteral("1.0");
                }

                nRet = DumpIDL(outfile, version);
            } else {
                qWarning("Wrong commandline syntax: <app> -dumpidl <idl file> [-version <x.y.z>]");
            }
            run = false;
            break;
        } else {
            unprocessed.append(cmds.at(i));
        }
    }

    if (run) {
        if (SUCCEEDED(CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED))) {
#ifdef Q_CC_MINGW
            // define GlobalOptions class ID locally for MinGW, since it's missing from the distribution
            static const CLSID CLSID_GlobalOptions =
                { 0x0000034B, 0x0000, 0x0000, { 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x46 } };
#endif
            // Disable C++ & SEH exception handling by the COM runtime for out-of-process COM controls.
            // Done to prevent silent crashes and enable crash dump generation.
            IGlobalOptions *globalOptions = nullptr;
            if (SUCCEEDED(CoCreateInstance(CLSID_GlobalOptions, nullptr, CLSCTX_INPROC_SERVER,
                                           IID_IGlobalOptions, reinterpret_cast<void **>(&globalOptions)))) {
                globalOptions->Set(COMGLB_EXCEPTION_HANDLING, COMGLB_EXCEPTION_DONOT_HANDLE_ANY);
                globalOptions->Release();
                globalOptions = nullptr;
            }

            {
                Arg args(unprocessed);
                qAxInit();
                if (runServer)
                    QAxFactory::startServer();
                nRet = ::main(args.argv.size() - 1, args.argv.data());
                QAxFactory::stopServer();
                qAxCleanup();
            }
            CoUninitialize();
        } else {
            qErrnoWarning("CoInitializeEx() failed.");
            nRet = -1;
        }
    }

    return nRet;
}
