/****************************************************************************
**
** Copyright (C) 2015 The Qt Company Ltd.
** Contact: http://www.qt.io/licensing/
**
** This file is part of the ActiveQt framework of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:BSD$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** BSD License Usage
** Alternatively, you may use this file under the terms of the BSD license
** as follows:
**
** "Redistribution and use in source and binary forms, with or without
** modification, are permitted provided that the following conditions are
** met:
**   * Redistributions of source code must retain the above copyright
**     notice, this list of conditions and the following disclaimer.
**   * 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.
**   * Neither the name of The Qt Company Ltd nor the names of its
**     contributors may be used to endorse or promote products derived
**     from this software without specific prior written permission.
**
**
** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
**
** $QT_END_LICENSE$
**
****************************************************************************/

#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 int keyCount = keys.count();
    if (!keyCount)
        return false;

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

    classRegistration = new DWORD[keyCount];
    int 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 (int i = 0; i < cmds.count(); ++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.count()) {
                const QString &outfile = cmds.at(i);
                ++i;
                QString version;
                if (i < cmds.count() && matchesOption(cmds.at(i), "version")) {
                    ++i;
                    if (i < cmds.count())
                        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;
}
