/* $Id: COMDefs.cpp $ */
/** @file
 * VBox Qt GUI - CInterface implementation.
 */

/*
 * Copyright (C) 2006-2023 Oracle and/or its affiliates.
 *
 * This file is part of VirtualBox base platform packages, as
 * available from https://www.virtualbox.org.
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation, in version 3 of the
 * License.
 *
 * This program 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
 * General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, see <https://www.gnu.org/licenses>.
 *
 * SPDX-License-Identifier: GPL-3.0-only
 */

/* Qt includes: */
#include <QSocketNotifier>

/* GUI includes: */
#include "COMDefs.h"

/* COM includes: */
#include "CVirtualBoxErrorInfo.h"

/* VirtualBox interface declarations: */
#include <VBox/com/VirtualBox.h>

/* Other VBox includes: */
#include <iprt/log.h>

#ifdef VBOX_WITH_XPCOM

/* Other VBox includes: */
# include <nsEventQueueUtils.h>
# include <nsIEventQueue.h>
# include <nsIExceptionService.h>

/* Mac OS X (Carbon mode) and OS/2 will notify the native queue
   internally in plevent.c. Because moc doesn't seems to respect
   #ifdefs, we still have to include the definition of the class.
   very silly. */
# if !defined (Q_OS_MAC)  && !defined (Q_OS_OS2)
XPCOMEventQSocketListener *COMBase::sSocketListener = 0;

# endif

/**
 *  Internal class to asynchronously handle IPC events on the GUI thread
 *  using the event queue socket FD and QSocketNotifier.
 */
class XPCOMEventQSocketListener : public QObject
{
    Q_OBJECT

public:

    XPCOMEventQSocketListener (nsIEventQueue *eq)
    {
        mEventQ = eq;
        mNotifier = new QSocketNotifier (mEventQ->GetEventQueueSelectFD(),
                                         QSocketNotifier::Read, this);
        QObject::connect (mNotifier, SIGNAL (activated (int)),
                          this, SLOT (processEvents()));
    }

    virtual ~XPCOMEventQSocketListener()
    {
        delete mNotifier;
    }

public slots:

    void processEvents() { mEventQ->ProcessPendingEvents(); }

private:

    QSocketNotifier *mNotifier;
    nsCOMPtr <nsIEventQueue> mEventQ;
};

#endif /* !defined (VBOX_WITH_XPCOM) */

/**
 *  Initializes COM/XPCOM.
 */
HRESULT COMBase::InitializeCOM(bool fGui)
{
    LogFlowFuncEnter();

    HRESULT rc = com::Initialize(fGui ? VBOX_COM_INIT_F_DEFAULT | VBOX_COM_INIT_F_GUI : VBOX_COM_INIT_F_DEFAULT);

#if defined (VBOX_WITH_XPCOM)

# if !defined (RT_OS_DARWIN) && !defined (RT_OS_OS2)

    if (NS_SUCCEEDED (rc))
    {
        nsCOMPtr <nsIEventQueue> eventQ;
        rc = NS_GetMainEventQ (getter_AddRefs (eventQ));
        if (NS_SUCCEEDED (rc))
        {
#  ifdef DEBUG
            BOOL isNative = FALSE;
            eventQ->IsQueueNative (&isNative);
            AssertMsg (isNative, ("The event queue must be native"));
#  endif
            BOOL isOnMainThread = FALSE;
            rc = eventQ->IsOnCurrentThread (&isOnMainThread);
            if (NS_SUCCEEDED (rc) && isOnMainThread)
            {
                sSocketListener = new XPCOMEventQSocketListener (eventQ);
            }
        }
    }

# endif /* !defined (RT_OS_DARWIN) && !defined (RT_OS_OS) */

#endif /* defined (VBOX_WITH_XPCOM) */

    if (FAILED (rc))
        CleanupCOM();

    AssertComRC (rc);

    LogFlowFunc (("rc=%08X\n", rc));
    LogFlowFuncLeave();
    return rc;

}

/**
 *  Cleans up COM/XPCOM.
 */
HRESULT COMBase::CleanupCOM()
{
    LogFlowFuncEnter();

    HRESULT rc = S_OK;

#if defined (VBOX_WITH_XPCOM)

    /* scope the code to make smart references are released before calling
     * com::Shutdown() */
    {
        nsCOMPtr <nsIEventQueue> eventQ;
        rc = NS_GetMainEventQ (getter_AddRefs (eventQ));
        if (NS_SUCCEEDED (rc))
        {
            BOOL isOnMainThread = FALSE;
            rc = eventQ->IsOnCurrentThread (&isOnMainThread);
            if (NS_SUCCEEDED (rc) && isOnMainThread)
            {
# if !defined (RT_OS_DARWIN) && !defined (RT_OS_OS2)
                if (sSocketListener)
                {
                    delete sSocketListener;
                    sSocketListener = NULL;
                }
# endif
            }
        }
    }

#endif /* defined (VBOX_WITH_XPCOM) */

    HRESULT rc2 = com::Shutdown();
    if (SUCCEEDED (rc))
        rc = rc2;

    AssertComRC (rc);

    LogFlowFunc (("rc=%08X\n", rc));
    LogFlowFuncLeave();
    return rc;
}

/* static */
void COMBase::ToSafeArray (const QVector <QString> &aVec,
                           com::SafeArray <BSTR> &aArr)
{
    aArr.reset (aVec.size());
    for (int i = 0; i < aVec.size(); ++ i)
        aArr [i] = SysAllocString ((const OLECHAR *)
            (aVec.at (i).isNull() ? 0 : aVec.at (i).utf16()));
}

/* static */
void COMBase::FromSafeArray (const com::SafeArray <BSTR> &aArr,
                             QVector <QString> &aVec)
{
    aVec.resize (static_cast <int> (aArr.size()));
    for (int i = 0; i < aVec.size(); ++ i)
        aVec [i] = QString::fromUtf16 (aArr [i]);
}

/* static */
void COMBase::ToSafeArray (const QVector <QUuid> &aVec,
                           com::SafeGUIDArray &aArr)
{
    AssertCompileSize (GUID, sizeof (QUuid));
    aArr.reset (aVec.size());
    for (int i = 0; i < aVec.size(); ++ i)
        aArr [i] = *(GUID*) &aVec [i];
}

/* static */
void COMBase::FromSafeArray (const com::SafeGUIDArray &aArr,
                             QVector <QUuid> &aVec)
{
    AssertCompileSize (GUID, sizeof (QUuid));
    aVec.resize (static_cast <int> (aArr.size()));
    for (int i = 0; i < aVec.size(); ++ i)
    {
#ifdef VBOX_WITH_XPCOM
        aVec [i] = *(QUuid*) &aArr [i];
#else
        /* No by-reference accessor, only by-value. So spell it out to avoid warnings. */
        GUID Tmp = aArr[i];
        aVec[i] = *(QUuid *)&Tmp;
#endif
    }
}

/* static */
void COMBase::ToSafeArray (const QVector <QUuid> &aVec,
                           com::SafeArray <BSTR> &aArr)
{
    aArr.reset (aVec.size());
    for (int i = 0; i < aVec.size(); ++ i)
        aArr [i] = SysAllocString ((const OLECHAR *)
            (aVec.at (i).isNull() ? 0 : aVec.at(i).toString().utf16()));
}

/* static */
void COMBase::FromSafeArray (const com::SafeArray <BSTR> &aArr,
                             QVector <QUuid> &aVec)
{
    aVec.resize (static_cast <int> (aArr.size()));
    for (int i = 0; i < aVec.size(); ++ i)
        aVec [i] = QUuid(QString::fromUtf16 (aArr [i]));
}

////////////////////////////////////////////////////////////////////////////////

void COMErrorInfo::init(const CVirtualBoxErrorInfo &info)
{
    if (info.isNull())
    {
        mIsNull = true;
        mIsBasicAvailable = false;
        mIsFullAvailable = false;
        mResultCode = S_OK;
        m_pNext = NULL;
        AssertMsgFailedReturnVoid(("error info is NULL!\n"));
    }

    bool gotSomething = false;
    bool gotAll = true;

    mResultCode = info.GetResultCode();
    gotSomething |= info.isOk();
    gotAll &= info.isOk();

    mInterfaceID = info.GetInterfaceID();
    gotSomething |= info.isOk();
    gotAll &= info.isOk();
    if (info.isOk())
        mInterfaceName = getInterfaceNameFromIID (mInterfaceID);

    mComponent = info.GetComponent();
    gotSomething |= info.isOk();
    gotAll &= info.isOk();

    mText = info.GetText();
    gotSomething |= info.isOk();
    gotAll &= info.isOk();

    m_pNext = NULL;

    CVirtualBoxErrorInfo next = info.GetNext();
    if (info.isOk() && !next.isNull())
    {
        m_pNext = new COMErrorInfo(next);
        Assert(m_pNext);
    }

    gotSomething |= info.isOk();
    gotAll &= info.isOk();

    mIsBasicAvailable = gotSomething;
    mIsFullAvailable = gotAll;

    mIsNull = !gotSomething;

    AssertMsg (gotSomething, ("Nothing to fetch!\n"));
}

void COMErrorInfo::copyFrom(const COMErrorInfo &x)
{
    mIsNull = x.mIsNull;
    mIsBasicAvailable = x.mIsBasicAvailable;
    mIsFullAvailable = x.mIsFullAvailable;

    mResultCode = x.mResultCode;
    mInterfaceID = x.mInterfaceID;
    mComponent = x.mComponent;
    mText = x.mText;

    if (x.m_pNext)
        m_pNext = new COMErrorInfo(*x.m_pNext);
    else
        m_pNext = NULL;

    mInterfaceName = x.mInterfaceName;
    mCalleeIID = x.mCalleeIID;
    mCalleeName = x.mCalleeName;
}

void COMErrorInfo::cleanup()
{
    if (m_pNext)
    {
        delete m_pNext;
        m_pNext = NULL;
    }
}

/**
 *  Fetches error info from the current thread.
 *  If callee is NULL, then error info is fetched in "interfaceless"
 *  manner (so calleeIID() and calleeName() will return null).
 *
 *  @param  callee
 *      pointer to the interface whose method returned an error
 *  @param  calleeIID
 *      UUID of the callee's interface. Ignored when callee is NULL
 */
void COMErrorInfo::fetchFromCurrentThread(IUnknown *callee, const GUID *calleeIID)
{
    mIsNull = true;
    mIsFullAvailable = mIsBasicAvailable = false;

    AssertReturnVoid(!callee || calleeIID);

    HRESULT rc = E_FAIL;

#if !defined(VBOX_WITH_XPCOM)

    if (callee)
    {
        ComPtr<IUnknown> iface(callee);
        ComPtr<ISupportErrorInfo> serr(iface);
        if (!serr)
            return;
        rc = serr->InterfaceSupportsErrorInfo(*calleeIID);
        if (!SUCCEEDED(rc))
            return;
    }

    ComPtr<IErrorInfo> err;
    rc = ::GetErrorInfo(0, err.asOutParam());
    if (rc == S_OK && err)
    {
        ComPtr<IVirtualBoxErrorInfo> info(err);
        if (info)
            init(CVirtualBoxErrorInfo(info));

        if (!mIsFullAvailable)
        {
            bool gotSomething = false;

            rc = err->GetGUID(COMBase::GUIDOut(mInterfaceID));
            gotSomething |= SUCCEEDED(rc);
            if (SUCCEEDED(rc))
                mInterfaceName = getInterfaceNameFromIID(mInterfaceID);

            rc = err->GetSource(COMBase::BSTROut(mComponent));
            gotSomething |= SUCCEEDED(rc);

            rc = err->GetDescription(COMBase::BSTROut(mText));
            gotSomething |= SUCCEEDED(rc);

            if (gotSomething)
                mIsBasicAvailable = true;

            mIsNull = !gotSomething;

            AssertMsg(gotSomething,("Nothing to fetch!\n"));
        }
    }

#else /* defined(VBOX_WITH_XPCOM) */

    nsCOMPtr<nsIExceptionService> es;
    es = do_GetService(NS_EXCEPTIONSERVICE_CONTRACTID, &rc);
    if (NS_SUCCEEDED(rc))
    {
        nsCOMPtr<nsIExceptionManager> em;
        rc = es->GetCurrentExceptionManager(getter_AddRefs(em));
        if (NS_SUCCEEDED(rc))
        {
            nsCOMPtr<nsIException> ex;
            rc = em->GetCurrentException(getter_AddRefs(ex));
            if (NS_SUCCEEDED(rc) && ex)
            {
                nsCOMPtr<IVirtualBoxErrorInfo> info;
                info = do_QueryInterface(ex, &rc);
                if (NS_SUCCEEDED(rc) && info)
                    init(CVirtualBoxErrorInfo(info));

                if (!mIsFullAvailable)
                {
                    bool gotSomething = false;

                    rc = ex->GetResult(&mResultCode);
                    gotSomething |= NS_SUCCEEDED(rc);

                    char *message = NULL; // utf8
                    rc = ex->GetMessage(&message);
                    gotSomething |= NS_SUCCEEDED(rc);
                    if (NS_SUCCEEDED(rc) && message)
                    {
                        mText = QString::fromUtf8(message);
                        nsMemory::Free(message);
                    }

                    if (gotSomething)
                        mIsBasicAvailable = true;

                    mIsNull = !gotSomething;

                    AssertMsg(gotSomething, ("Nothing to fetch!\n"));
                }

                // set the exception to NULL (to emulate Win32 behavior)
                em->SetCurrentException(NULL);

                rc = NS_OK;
            }
        }
    }

    AssertComRC(rc);

#endif /* !defined(VBOX_WITH_XPCOM) */

    if (callee && calleeIID && mIsBasicAvailable)
    {
        mCalleeIID = COMBase::ToQUuid(*calleeIID);
        mCalleeName = getInterfaceNameFromIID(mCalleeIID);
    }
}

// static
QString COMErrorInfo::getInterfaceNameFromIID (const QUuid &id)
{
    QString name;

    com::GetInterfaceNameByIID (COMBase::GUIDIn (id), COMBase::BSTROut (name));

    return name;
}

#if defined (VBOX_WITH_XPCOM)
#include "COMDefs.moc"
#endif

