/****************************************************************************
**
** Copyright (C) 2015 The Qt Company Ltd.
** Contact: http://www.qt.io/licensing/
**
** This file is part of the plugins of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:LGPL3$
** 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 http://www.qt.io/terms-conditions. For further
** information use the contact form at http://www.qt.io/contact-us.
**
** GNU Lesser General Public License Usage
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 3 as published by the Free Software
** Foundation and appearing in the file LICENSE.LGPLv3 included in the
** packaging of this file. Please review the following information to
** ensure the GNU Lesser General Public License version 3 requirements
** will be met: https://www.gnu.org/licenses/lgpl.html.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 2.0 or later as published by the Free
** Software Foundation and appearing in the file LICENSE.GPL included in
** the packaging of this file. Please review the following information to
** ensure the GNU General Public License version 2.0 requirements will be
** met: http://www.gnu.org/licenses/gpl-2.0.html.
**
** $QT_END_LICENSE$
**
****************************************************************************/

#include "qwinrtintegration.h"
#include "qwinrtwindow.h"
#include "qwinrteventdispatcher.h"
#include "qwinrtbackingstore.h"
#include "qwinrtscreen.h"
#include "qwinrtinputcontext.h"
#include "qwinrtservices.h"
#include "qwinrteglcontext.h"
#include "qwinrtfontdatabase.h"
#include "qwinrttheme.h"
#include "qwinrtclipboard.h"
#ifndef QT_NO_DRAGANDDROP
#include "qwinrtdrag.h"
#endif

#include <QtGui/QOffscreenSurface>
#include <QtGui/QOpenGLContext>
#include <QtGui/QSurface>

#include <QtPlatformSupport/private/qeglpbuffer_p.h>
#include <qpa/qwindowsysteminterface.h>
#include <qpa/qplatformwindow.h>
#include <qpa/qplatformoffscreensurface.h>

#include <qfunctions_winrt.h>

#include <functional>
#include <wrl.h>
#include <windows.ui.xaml.h>
#include <windows.applicationmodel.h>
#include <windows.applicationmodel.core.h>
#include <windows.ui.core.h>
#include <windows.ui.viewmanagement.h>
#include <windows.graphics.display.h>

#if WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_PHONE_APP)
#  include <windows.phone.ui.input.h>
#  if _MSC_VER >= 1900
#    include <windows.foundation.metadata.h>
     using namespace ABI::Windows::Foundation::Metadata;
#  endif
#endif


using namespace Microsoft::WRL;
using namespace Microsoft::WRL::Wrappers;
using namespace ABI::Windows::Foundation;
using namespace ABI::Windows::ApplicationModel;
using namespace ABI::Windows::ApplicationModel::Core;
using namespace ABI::Windows::UI;
using namespace ABI::Windows::UI::Core;
using namespace ABI::Windows::UI::ViewManagement;
using namespace ABI::Windows::Graphics::Display;
using namespace ABI::Windows::ApplicationModel::Core;
#if WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_PHONE_APP)
using namespace ABI::Windows::Phone::UI::Input;
#endif

typedef IEventHandler<IInspectable *> ResumeHandler;
typedef IEventHandler<SuspendingEventArgs *> SuspendHandler;
#if WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_PHONE_APP)
typedef IEventHandler<BackPressedEventArgs*> BackPressedHandler;
typedef IEventHandler<CameraEventArgs*> CameraButtonHandler;
#endif

QT_BEGIN_NAMESPACE

typedef HRESULT (__stdcall ICoreApplication::*CoreApplicationCallbackRemover)(EventRegistrationToken);
uint qHash(CoreApplicationCallbackRemover key) { void *ptr = *(void **)(&key); return qHash(ptr); }
#if WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_PHONE_APP)
typedef HRESULT (__stdcall IHardwareButtonsStatics::*HardwareButtonsCallbackRemover)(EventRegistrationToken);
uint qHash(HardwareButtonsCallbackRemover key) { void *ptr = *(void **)(&key); return qHash(ptr); }
typedef HRESULT (__stdcall IHardwareButtonsStatics2::*HardwareButtons2CallbackRemover)(EventRegistrationToken);
uint qHash(HardwareButtons2CallbackRemover key) { void *ptr = *(void **)(&key); return qHash(ptr); }
#endif

class QWinRTIntegrationPrivate
{
public:
    QPlatformFontDatabase *fontDatabase;
    QPlatformServices *platformServices;
    QPlatformClipboard *clipboard;
    QWinRTScreen *mainScreen;
    QScopedPointer<QWinRTInputContext> inputContext;

    ComPtr<ICoreApplication> application;
    QHash<CoreApplicationCallbackRemover, EventRegistrationToken> applicationTokens;
#if WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_PHONE_APP)
    ComPtr<IHardwareButtonsStatics> hardwareButtons;
    QHash<HardwareButtonsCallbackRemover, EventRegistrationToken> buttonsTokens;
    ComPtr<IHardwareButtonsStatics2> cameraButtons;
    QHash<HardwareButtons2CallbackRemover, EventRegistrationToken> cameraTokens;
    boolean hasHardwareButtons;
    bool cameraHalfPressed : 1;
    bool cameraPressed : 1;
#endif
};

QWinRTIntegration::QWinRTIntegration() : d_ptr(new QWinRTIntegrationPrivate)
{
    Q_D(QWinRTIntegration);

    d->fontDatabase = new QWinRTFontDatabase;

    HRESULT hr;
    hr = RoGetActivationFactory(HString::MakeReference(RuntimeClass_Windows_ApplicationModel_Core_CoreApplication).Get(),
                                IID_PPV_ARGS(&d->application));
    Q_ASSERT_SUCCEEDED(hr);
    hr = d->application->add_Suspending(Callback<SuspendHandler>(this, &QWinRTIntegration::onSuspended).Get(),
                                        &d->applicationTokens[&ICoreApplication::remove_Suspending]);
    Q_ASSERT_SUCCEEDED(hr);
    hr = d->application->add_Resuming(Callback<ResumeHandler>(this, &QWinRTIntegration::onResume).Get(),
                                      &d->applicationTokens[&ICoreApplication::remove_Resuming]);
    Q_ASSERT_SUCCEEDED(hr);

#if WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_PHONE_APP)
#if _MSC_VER >= 1900
    d->hasHardwareButtons = false;
    ComPtr<IApiInformationStatics> apiInformationStatics;
    hr = RoGetActivationFactory(HString::MakeReference(RuntimeClass_Windows_Foundation_Metadata_ApiInformation).Get(),
                                IID_PPV_ARGS(&apiInformationStatics));

    if (SUCCEEDED(hr)) {
        const HStringReference valueRef(L"Windows.Phone.UI.Input.HardwareButtons");
        hr = apiInformationStatics->IsTypePresent(valueRef.Get(), &d->hasHardwareButtons);
    }
#else
    d->hasHardwareButtons = true;
#endif // _MSC_VER >= 1900

    if (d->hasHardwareButtons) {
        hr = RoGetActivationFactory(HString::MakeReference(RuntimeClass_Windows_Phone_UI_Input_HardwareButtons).Get(),
                                    IID_PPV_ARGS(&d->hardwareButtons));
        Q_ASSERT_SUCCEEDED(hr);
        hr = d->hardwareButtons->add_BackPressed(Callback<BackPressedHandler>(this, &QWinRTIntegration::onBackButtonPressed).Get(),
                                                 &d->buttonsTokens[&IHardwareButtonsStatics::remove_BackPressed]);
        Q_ASSERT_SUCCEEDED(hr);

        hr = RoGetActivationFactory(HString::MakeReference(RuntimeClass_Windows_Phone_UI_Input_HardwareButtons).Get(),
                                    IID_PPV_ARGS(&d->cameraButtons));
        Q_ASSERT_SUCCEEDED(hr);
        if (qEnvironmentVariableIntValue("QT_QPA_ENABLE_CAMERA_KEYS")) {
            hr = d->cameraButtons->add_CameraPressed(Callback<CameraButtonHandler>(this, &QWinRTIntegration::onCameraPressed).Get(),
                                                     &d->cameraTokens[&IHardwareButtonsStatics2::remove_CameraPressed]);
            Q_ASSERT_SUCCEEDED(hr);
            hr = d->cameraButtons->add_CameraHalfPressed(Callback<CameraButtonHandler>(this, &QWinRTIntegration::onCameraHalfPressed).Get(),
                                                         &d->cameraTokens[&IHardwareButtonsStatics2::remove_CameraHalfPressed]);
            Q_ASSERT_SUCCEEDED(hr);
            hr = d->cameraButtons->add_CameraReleased(Callback<CameraButtonHandler>(this, &QWinRTIntegration::onCameraReleased).Get(),
                                                      &d->cameraTokens[&IHardwareButtonsStatics2::remove_CameraReleased]);
            Q_ASSERT_SUCCEEDED(hr);
        }
        d->cameraPressed = false;
        d->cameraHalfPressed = false;
    }
#endif // WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_PHONE_APP)


    QEventDispatcherWinRT::runOnXamlThread([d]() {
        d->mainScreen = new QWinRTScreen;
        d->inputContext.reset(new QWinRTInputContext(d->mainScreen));
        return S_OK;
    });

    screenAdded(d->mainScreen);
    d->platformServices = new QWinRTServices;
    d->clipboard = new QWinRTClipboard;
}

QWinRTIntegration::~QWinRTIntegration()
{
    Q_D(QWinRTIntegration);
    HRESULT hr;

#if WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_PHONE_APP)
    if (d->hasHardwareButtons) {
        for (QHash<HardwareButtonsCallbackRemover, EventRegistrationToken>::const_iterator i = d->buttonsTokens.begin(); i != d->buttonsTokens.end(); ++i) {
            hr = (d->hardwareButtons.Get()->*i.key())(i.value());
            Q_ASSERT_SUCCEEDED(hr);
        }
        for (QHash<HardwareButtons2CallbackRemover, EventRegistrationToken>::const_iterator i = d->cameraTokens.begin(); i != d->cameraTokens.end(); ++i) {
            hr = (d->cameraButtons.Get()->*i.key())(i.value());
            Q_ASSERT_SUCCEEDED(hr);
        }
    }
#endif
    // Do not execute this on Windows Phone as the application is already
    // shutting down and trying to unregister suspending/resume handler will
    // cause exceptions and assert in debug mode
#ifndef Q_OS_WINPHONE
    for (QHash<CoreApplicationCallbackRemover, EventRegistrationToken>::const_iterator i = d->applicationTokens.begin(); i != d->applicationTokens.end(); ++i) {
        hr = (d->application.Get()->*i.key())(i.value());
        Q_ASSERT_SUCCEEDED(hr);
    }
#endif
    destroyScreen(d->mainScreen);
    Windows::Foundation::Uninitialize();
}

bool QWinRTIntegration::succeeded() const
{
    Q_D(const QWinRTIntegration);
    return d->mainScreen;
}

QAbstractEventDispatcher *QWinRTIntegration::createEventDispatcher() const
{
    return new QWinRTEventDispatcher;
}

void QWinRTIntegration::initialize()
{
    Q_D(const QWinRTIntegration);
    QEventDispatcherWinRT::runOnXamlThread([d]() {
        d->mainScreen->initialize();
        return S_OK;
    });
}

bool QWinRTIntegration::hasCapability(QPlatformIntegration::Capability cap) const
{
    switch (cap) {
    case ThreadedPixmaps:
    case OpenGL:
    case ApplicationState:
    case NonFullScreenWindows:
    case MultipleWindows:
    case RasterGLSurface:
        return true;
    default:
        return QPlatformIntegration::hasCapability(cap);
    }
}

QVariant QWinRTIntegration::styleHint(StyleHint hint) const
{
    return QWinRTTheme::styleHint(hint);
}

QPlatformWindow *QWinRTIntegration::createPlatformWindow(QWindow *window) const
{
    return new QWinRTWindow(window);
}

QPlatformBackingStore *QWinRTIntegration::createPlatformBackingStore(QWindow *window) const
{
    return new QWinRTBackingStore(window);
}

QPlatformOpenGLContext *QWinRTIntegration::createPlatformOpenGLContext(QOpenGLContext *context) const
{
    return new QWinRTEGLContext(context);
}

QPlatformFontDatabase *QWinRTIntegration::fontDatabase() const
{
    Q_D(const QWinRTIntegration);
    return d->fontDatabase;
}

QPlatformInputContext *QWinRTIntegration::inputContext() const
{
    Q_D(const QWinRTIntegration);
    return d->inputContext.data();
}

QPlatformServices *QWinRTIntegration::services() const
{
    Q_D(const QWinRTIntegration);
    return d->platformServices;
}

QPlatformClipboard *QWinRTIntegration::clipboard() const
{
    Q_D(const QWinRTIntegration);
    return d->clipboard;
}

#ifndef QT_NO_DRAGANDDROP
QPlatformDrag *QWinRTIntegration::drag() const
{
#if _MSC_VER >= 1900
    return QWinRTDrag::instance();
#else
    return QPlatformIntegration::drag();
#endif
}
#endif // QT_NO_DRAGANDDROP

Qt::KeyboardModifiers QWinRTIntegration::queryKeyboardModifiers() const
{
    Q_D(const QWinRTIntegration);
    return d->mainScreen->keyboardModifiers();
}

QStringList QWinRTIntegration::themeNames() const
{
    return QStringList(QLatin1String("winrt"));
}

QPlatformTheme *QWinRTIntegration::createPlatformTheme(const QString &name) const
{
    if (name == QLatin1String("winrt"))
        return new QWinRTTheme();

    return 0;
}

// System-level integration points

#if WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_PHONE_APP)
HRESULT QWinRTIntegration::onBackButtonPressed(IInspectable *, IBackPressedEventArgs *args)
{
    Q_D(QWinRTIntegration);
    QWindow *window = d->mainScreen->topWindow();
    QWindowSystemInterface::setSynchronousWindowSystemEvents(true);
    const bool pressed = QWindowSystemInterface::handleExtendedKeyEvent(window, QEvent::KeyPress, Qt::Key_Back, Qt::NoModifier,
                                                                        0, 0, 0, QString(), false, 1, false);
    const bool released = QWindowSystemInterface::handleExtendedKeyEvent(window, QEvent::KeyRelease, Qt::Key_Back, Qt::NoModifier,
                                                                         0, 0, 0, QString(), false, 1, false);
    QWindowSystemInterface::setSynchronousWindowSystemEvents(false);
    args->put_Handled(pressed || released);
    return S_OK;
}

HRESULT QWinRTIntegration::onCameraPressed(IInspectable *, ICameraEventArgs *)
{
    Q_D(QWinRTIntegration);
    QWindow *window = d->mainScreen->topWindow();
    QWindowSystemInterface::handleExtendedKeyEvent(window, QEvent::KeyPress, Qt::Key_Camera, Qt::NoModifier,
                                                   0, 0, 0, QString(), false, 1, false);
    d->cameraPressed = true;
    return S_OK;
}

HRESULT QWinRTIntegration::onCameraHalfPressed(IInspectable *, ICameraEventArgs *)
{
    Q_D(QWinRTIntegration);
    QWindow *window = d->mainScreen->topWindow();
    QWindowSystemInterface::handleExtendedKeyEvent(window, QEvent::KeyPress, Qt::Key_CameraFocus, Qt::NoModifier,
                                                   0, 0, 0, QString(), false, 1, false);
    d->cameraHalfPressed = true;
    return S_OK;
}

HRESULT QWinRTIntegration::onCameraReleased(IInspectable *, ICameraEventArgs *)
{
    Q_D(QWinRTIntegration);
    QWindow *window = d->mainScreen->topWindow();
    if (d->cameraHalfPressed)
        QWindowSystemInterface::handleExtendedKeyEvent(window, QEvent::KeyRelease, Qt::Key_CameraFocus, Qt::NoModifier,
                                                       0, 0, 0, QString(), false, 1, false);

    if (d->cameraPressed)
        QWindowSystemInterface::handleExtendedKeyEvent(window, QEvent::KeyRelease, Qt::Key_Camera, Qt::NoModifier,
                                                       0, 0, 0, QString(), false, 1, false);
    d->cameraHalfPressed = false;
    d->cameraPressed = false;
    return S_OK;
}
#endif // WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_PHONE_APP)

HRESULT QWinRTIntegration::onSuspended(IInspectable *, ISuspendingEventArgs *)
{
    QWindowSystemInterface::handleApplicationStateChanged(Qt::ApplicationSuspended);
    QWindowSystemInterface::flushWindowSystemEvents();
    return S_OK;
}

HRESULT QWinRTIntegration::onResume(IInspectable *, IInspectable *)
{
    // First the system invokes onResume and then changes
    // the visibility of the screen to be active.
    QWindowSystemInterface::handleApplicationStateChanged(Qt::ApplicationHidden);
    return S_OK;
}

QPlatformOffscreenSurface *QWinRTIntegration::createPlatformOffscreenSurface(QOffscreenSurface *surface) const
{
    QEGLPbuffer *pbuffer = nullptr;
    HRESULT hr = QEventDispatcherWinRT::runOnXamlThread([&pbuffer, surface]() {
        pbuffer = new QEGLPbuffer(QWinRTEGLContext::display(), surface->requestedFormat(), surface);
        return S_OK;
    });
    if (hr == UI_E_WINDOW_CLOSED) {
        // This is only used for shutdown of applications.
        // In case we do not return an empty surface the scenegraph will try
        // to create a new native window during application exit causing crashes
        // or assertions.
        return new QPlatformOffscreenSurface(surface);
    }

    return pbuffer;
}


QT_END_NAMESPACE
