///////////////////////////////////////////////////////////////////////////////
// Name:        src/msw/rt/utilsrt.cpp
// Purpose:     Windows Runtime Objects helper functions and objects
// Author:      Tobias Taschner
// Created:     2015-09-05
// Copyright:   (c) 2015 wxWidgets development team
// Licence:     wxWindows licence
///////////////////////////////////////////////////////////////////////////////

// ============================================================================
// Declarations
// ============================================================================

// ----------------------------------------------------------------------------
// headers
// ----------------------------------------------------------------------------

// For compilers that support precompilation, includes "wx.h".
#include "wx/wxprec.h"

#include "wx/msw/rt/utils.h"

#if wxUSE_WINRT

#ifndef WX_PRECOMP
    #include "wx/log.h"
#endif //WX_PRECOMP

#include <roapi.h>

#include "wx/dynlib.h"
#include "wx/utils.h"
#include "wx/module.h"

// Core function typedefs
typedef HRESULT (__stdcall *PFNWXROINITIALIZE)(RO_INIT_TYPE initType);
typedef void (__stdcall *PFNWXROUNINITIALIZE)();
typedef HRESULT (__stdcall *PFNWXROGETACTIVATIONFACTORY)(
    HSTRING activatableClassId, REFIID iid, void ** factory);

// String function typedefs
typedef HRESULT (__stdcall *PFNWXWINDOWSCREATESTRINGREFERENCE)(
    PCWSTR sourceString,
    UINT32 length,
    HSTRING_HEADER * hstringHeader,
    HSTRING * string
);
typedef HRESULT (__stdcall *PFNWXWINDOWSDELETESTRING)(HSTRING string);

namespace wxWinRT
{

//
// RTCore
//

class RTCore
{
public:
    static RTCore& Get()
    {
        if ( ms_isAvailable == -1 )
        {
            if ( !ms_rtcore.Initialize() )
            {
                ms_isAvailable = 0;
            }
            else
                ms_isAvailable = 1;
        }

        return ms_rtcore;
    }

    static bool IsAvailable()
    {
        if (ms_isAvailable == -1)
            Get();

        return (ms_isAvailable == 1);
    }

    PFNWXROINITIALIZE RoInitialize;
    PFNWXROUNINITIALIZE RoUninitialize;
    PFNWXROGETACTIVATIONFACTORY RoGetActivationFactory;
    PFNWXWINDOWSCREATESTRINGREFERENCE WindowsCreateStringReference;
    PFNWXWINDOWSDELETESTRING WindowsDeleteString;

    bool Initialize()
    {
#define RESOLVE_RT_FUNCTION(dll, type, funcname)                        \
    funcname = (type)dll.GetSymbol(wxT(#funcname));                     \
    if ( !funcname )                                                    \
        return false

#define RESOLVE_RTCORE_FUNCTION(type, funcname) \
    RESOLVE_RT_FUNCTION(m_dllCore, type, funcname)

#define RESOLVE_RTSTRING_FUNCTION(type, funcname) \
    RESOLVE_RT_FUNCTION(m_dllString, type, funcname)

        // WinRT is only available in Windows 8 or newer
        if (!wxCheckOsVersion(6, 2))
            return false;

        // Initialize core functions
        if (!m_dllCore.Load("api-ms-win-core-winrt-l1-1-0.dll"))
            return false;

        RESOLVE_RTCORE_FUNCTION(PFNWXROINITIALIZE, RoInitialize);
        RESOLVE_RTCORE_FUNCTION(PFNWXROUNINITIALIZE, RoUninitialize);
        RESOLVE_RTCORE_FUNCTION(PFNWXROGETACTIVATIONFACTORY, RoGetActivationFactory);

        // Initialize string functions
        if (!m_dllString.Load("api-ms-win-core-winrt-string-l1-1-0.dll"))
            return false;

        RESOLVE_RTSTRING_FUNCTION(PFNWXWINDOWSCREATESTRINGREFERENCE, WindowsCreateStringReference);
        RESOLVE_RTSTRING_FUNCTION(PFNWXWINDOWSDELETESTRING, WindowsDeleteString);

        return true;

#undef RESOLVE_RT_FUNCTION
#undef RESOLVE_RTCORE_FUNCTION
#undef RESOLVE_RTSTRING_FUNCTION
    }

    void UnloadModules()
    {
        m_dllCore.Unload();
        m_dllString.Unload();
    }

    static void Uninitalize()
    {
        if (ms_isAvailable == 1)
        {
            Get().UnloadModules();
            ms_isAvailable = -1;
        }
    }

private:
    RTCore()
    {

    }

    wxDynamicLibrary m_dllCore;
    wxDynamicLibrary m_dllString;

    static RTCore ms_rtcore;
    static int ms_isAvailable;

    wxDECLARE_NO_COPY_CLASS(RTCore);
};

RTCore RTCore::ms_rtcore;
int RTCore::ms_isAvailable = -1;

//
// wxWinRT::TempStringRef
//

TempStringRef::TempStringRef(const wxString &str)
    : m_hstring(nullptr), m_header()
{
    if ( !RTCore::IsAvailable() )
    {
        wxLogDebug("Can not create string reference without WinRT");
        return;
    }

    // This creates a fast-pass string which must not be deleted using WindowsDeleteString
    HRESULT hr = RTCore::Get().WindowsCreateStringReference(
        str.wc_str(), str.length(),
        &m_header, &m_hstring);
    if ( FAILED(hr) )
        wxLogDebug("Could not create string reference %.8x", hr);
}

//
// wrapper functions
//

bool IsAvailable()
{
    return RTCore::IsAvailable();
}

bool Initialize()
{
    if ( !RTCore::IsAvailable() )
        return false;

    HRESULT hr = RTCore::Get().RoInitialize(RO_INIT_SINGLETHREADED);
    if ( FAILED(hr) )
    {
        wxLogDebug("RoInitialize failed %.8x", hr);
        return false;
    }
    else
        return true;
}

void Uninitialize()
{
    if ( !RTCore::IsAvailable() )
        return;

    RTCore::Get().RoUninitialize();
}

HRESULT GetActivationFactory(const wxString& activatableClassId, REFIID iid, void ** factory)
{
    if ( !RTCore::IsAvailable() )
        return CLASS_E_CLASSNOTAVAILABLE;

    HRESULT hr = RTCore::Get().RoGetActivationFactory(TempStringRef(activatableClassId), iid, factory);
    if ( FAILED(hr) )
    {
        wxLogDebug("RoGetActivationFactory failed %.8x", hr);
    }
    return hr;
}

// ----------------------------------------------------------------------------
// Module ensuring all global/singleton objects are destroyed on shutdown.
// ----------------------------------------------------------------------------

class RTModule : public wxModule
{
public:
    RTModule()
    {
    }

    virtual bool OnInit() override
    {
        return true;
    }

    virtual void OnExit() override
    {
        RTCore::Uninitalize();
    }

private:
    wxDECLARE_DYNAMIC_CLASS(RTModule);
};

wxIMPLEMENT_DYNAMIC_CLASS(RTModule, wxModule);

} // namespace wxWinRT

#endif // wxUSE_WINRT
