///////////////////////////////////////////////////////////////////////////////
// Copyright (c) Electronic Arts Inc. All rights reserved.
///////////////////////////////////////////////////////////////////////////////

#include "EABase/eabase.h"
#if (defined(EA_PLATFORM_MICROSOFT) && !defined(CS_UNDEFINED_STRING) && !EA_WINAPI_FAMILY_PARTITION(EA_WINAPI_PARTITION_DESKTOP))
#include <EAAssert/eaassert.h>
#include <EAMain/EAMain.h>
#include <EAStdC/EASprintf.h>
#include <EAStdC/EAString.h>

EA_DISABLE_ALL_VC_WARNINGS()
#pragma warning(disable: 4472 4355)  // additional warnings generated by XDK with VS2015
#include <memory>
#ifndef WIN32_LEAN_AND_MEAN
    #define WIN32_LEAN_AND_MEAN
#endif
#include <Windows.h>

#if defined EA_PLATFORM_CAPILANO
    #if defined EAMAIN_CAPILANO_DX12
        #include <d3d12_x.h>
    #else
        #include <d3d11_x.h>
    #endif
#endif
EA_RESTORE_ALL_VC_WARNINGS()

namespace EA
{
namespace EAMain
{

// Application - implements the required functionality for a application
ref class ApplicationView sealed : public Windows::ApplicationModel::Core::IFrameworkView
{
public:
    // IFrameworkView Methods
    virtual void Initialize(Windows::ApplicationModel::Core::CoreApplicationView^ applicationView);
    virtual void SetWindow(Windows::UI::Core::CoreWindow^ window) {}
    virtual void Load(Platform::String^ entryPoint);
    virtual void Run();
    virtual void Uninitialize();
private:
    char *mCommandLine;
#if EA_PLATFORM_CAPILANO
    #if defined EAMAIN_CAPILANO_DX12
        ID3D12CommandQueue* mpCommandQueue;
    #else
        ID3DXboxPerformanceContext* mpD3DXboxPerfContext;
    #endif
#endif

    void CreateDeviceResources();
    void OnActivated( Windows::ApplicationModel::Core::CoreApplicationView^ applicationView, Windows::ApplicationModel::Activation::IActivatedEventArgs^ args );
};

// ApplicationSource - responsible for creating the Application instance and passing it back to the system
ref class ApplicationViewSource : Windows::ApplicationModel::Core::IFrameworkViewSource
{
public:
    virtual Windows::ApplicationModel::Core::IFrameworkView^ CreateView()
    {
        return ref new ApplicationView();
    }
};

void OutputAppTransition(const char* transition, const wchar_t* sender, const wchar_t* args)
{
    char msg[1024];

    EA::StdC::Snprintf(msg, EAArrayCount(msg), "****\n  app is %s.\n  sender: ", transition);
    EA::StdC::Strlcat(msg, sender, EAArrayCount(msg));
    EA::StdC::Strlcat(msg, "\n  args: ", EAArrayCount(msg));
    EA::StdC::Strlcat(msg, args, EAArrayCount(msg));
    EA::StdC::Strlcat(msg, "\n****\n", EAArrayCount(msg));

    OutputDebugStringA(msg);
}

void ApplicationView::CreateDeviceResources()
{
#if defined EA_PLATFORM_CAPILANO
    #if defined EAMAIN_CAPILANO_DX12
        ID3D12Device* pD3DDevice;

        HRESULT hr = D3D12CreateDevice(
            nullptr,                    // specify null to use the default adapter
            D3D_FEATURE_LEVEL_11_0,
            __uuidof(ID3D12Device),
            (void**)&pD3DDevice
        );

        if (FAILED(hr))
        {
            OutputDebugStringA("Failed to create device. Suspending will not work for this application.");
            return;
        }

        D3D12XBOX_COMMAND_QUEUE_DESC queueDesc = {};
        queueDesc.Type = D3D12_COMMAND_LIST_TYPE_DIRECT;
        queueDesc.Flags = D3D12_COMMAND_QUEUE_FLAG_NONE;
        queueDesc.EngineOrPipeIndex = 0;
        queueDesc.QueueIndex = 0;

        hr = pD3DDevice->CreateCommandQueueX(&queueDesc, __uuidof(ID3D12CommandQueue), (void**)&mpCommandQueue);
        if (FAILED(hr))
        {
            OutputDebugStringA("Failed to create command queue. Suspending will not work for this application.");
        }

        if (pD3DDevice) { pD3DDevice->Release(); pD3DDevice = NULL; }
    #else
        // This flag adds support for surfaces with a different color channel ordering than the API default.
        // It is recommended usage, and is required for compatibility with Direct2D.
        UINT creationFlags = D3D11_CREATE_DEVICE_INSTRUMENTED;

        // This array defines the set of DirectX hardware feature levels this app will support.
        // Note the ordering should be preserved.
        // Don't forget to declare your application's minimum required feature level in its
        // description.  All applications are assumed to support 9.1 unless otherwise stated.
        D3D_FEATURE_LEVEL featureLevels[] =
        {
            D3D_FEATURE_LEVEL_11_0
        };

        // Create the DX11 API device object, and get a corresponding context.
        ID3D11Device* pD3DDevice;
        ID3D11DeviceContext* pD3DDeviceContext;

        HRESULT hr = D3D11CreateDevice(
            nullptr,                    // specify null to use the default adapter
            D3D_DRIVER_TYPE_HARDWARE,
            nullptr,                    // leave as nullptr unless software device
            creationFlags,              // optionally set debug and Direct2D compatibility flags
            featureLevels,              // list of feature levels this app can support
            ARRAYSIZE(featureLevels), // number of entries in above list
            D3D11_SDK_VERSION,          // always set this to D3D11_SDK_VERSION
            &pD3DDevice,                // returns the Direct3D device created
            NULL,                       // returns feature level of device created
            &pD3DDeviceContext          // returns the device immediate context
        );

        if (FAILED(hr))
        {
            OutputDebugStringA("Failed to create device. Suspending will not work for this application.");
            return;
        }

            hr = pD3DDevice->QueryInterface(__uuidof(mpD3DXboxPerfContext), (void **)&mpD3DXboxPerfContext);

        if (FAILED(hr))
        {
            OutputDebugStringA("Failed to get perfcontext. Suspending will not work for this application.");
        }

        if (pD3DDevice) { pD3DDevice->Release(); pD3DDevice = NULL; }
        if (pD3DDeviceContext) { pD3DDeviceContext->Release(); pD3DDeviceContext = NULL; }
    #endif
#else
    // Do nothing
#endif
}

// Called by the system.  Perform application initialization here,
// hooking application wide events, etc.
void ApplicationView::Initialize(Windows::ApplicationModel::Core::CoreApplicationView^ applicationView)
{
    using namespace Platform;
    using namespace Windows::Foundation;
    using namespace Windows::ApplicationModel;
    using namespace Windows::ApplicationModel::Core;
    using namespace Windows::ApplicationModel::Activation;

    // Creates any resources required by the platform to run an application / enable a particular feature set (like GPU profiling or suspend/resume)
    CreateDeviceResources();

#pragma warning(push)
// Disables warning for MS class 'Windows::Foundation::TypedEventHandler<TSender,TResult>::{ctor}::__abi_PointerToMemberCapture'
// "layout of class may have changed from a previous version of the compiler due to better packing of member 'Windows::Foundation::TypedEventHandler<TSender,TResult>::{ctor}::__abi_PointerToMemberCapture::member'"
#pragma warning(disable:4371)
    applicationView->Activated += ref new Windows::Foundation::TypedEventHandler< CoreApplicationView^, IActivatedEventArgs^ >( this, &ApplicationView::OnActivated );
#pragma warning(pop)
    CoreApplication::Suspending += ref new EventHandler<SuspendingEventArgs^>([this](Object^ sender, SuspendingEventArgs^ args) {
        OutputAppTransition("suspending", sender ? sender->ToString()->Data() : L"NULL", args ? args->ToString()->Data() : L"NULL");
        #if defined EA_PLATFORM_CAPILANO
            #if defined EAMAIN_CAPILANO_DX12
                mpCommandQueue->SuspendX(0);
            #else
                mpD3DXboxPerfContext->Suspend(0);
            #endif
        #endif
    });
    CoreApplication::Resuming += ref new EventHandler<Object^>([this](Object^ sender, Object^ args) {
        OutputAppTransition("resuming", sender ? sender->ToString()->Data() : L"NULL", args ? args->ToString()->Data() : L"NULL");
        #if defined EA_PLATFORM_CAPILANO
            #if defined EAMAIN_CAPILANO_DX12
                mpCommandQueue->ResumeX();
            #else
                mpD3DXboxPerfContext->Resume();
            #endif
        #endif
    });
    CoreApplication::Exiting += ref new EventHandler<Object^>([](Object^ sender, Object^ args) {
        OutputAppTransition("exiting", sender ? sender->ToString()->Data() : L"NULL", args ? args->ToString()->Data() : L"NULL");
    });
}

static char *ConvertLaunchArgsToMultibyte(LPCWSTR rawArgumentString, int rawArgumentStringLength)
{
    int bufferSize = WideCharToMultiByte(
            CP_UTF8,
            0,
            rawArgumentString,
            rawArgumentStringLength,
            NULL,
            0,
            NULL,
            NULL);

    char *commandLine = static_cast<char *>(calloc(bufferSize + 1, 1));
    int rv = WideCharToMultiByte(
            CP_UTF8,
            0,
            rawArgumentString,
            rawArgumentStringLength,
            commandLine,
            bufferSize + 1,
            NULL,
            NULL);

    commandLine[bufferSize] = 0;
    EA_ASSERT(rv == bufferSize);
    EA_UNUSED(rv); // avoids warnings in opt builds regarding unused variables

    return commandLine;
}

static char *ReadArgsFromFile()
{
    FILE *fp = fopen("EAMainArgsFile.txt", "rb");

    if (fp == NULL)
    {
        goto error_return;
    }

    size_t fileSize;
    fseek(fp, 0, SEEK_END);
    fileSize = static_cast<size_t>(ftell(fp));
    fseek(fp, 0, SEEK_SET);

    char *argsBuffer = static_cast<char *>(calloc(fileSize + 1, 1));

    if (fread(argsBuffer, 1, fileSize, fp) != fileSize)
    {
        goto error_return_free_buffer;
    }

    return argsBuffer;

error_return_free_buffer:
    free(argsBuffer);
    fclose(fp);

error_return:
    return static_cast<char *>(calloc(1, 1));
}

void ApplicationView::OnActivated( Windows::ApplicationModel::Core::CoreApplicationView^ applicationView, Windows::ApplicationModel::Activation::IActivatedEventArgs^ args )
{
    if (args->Kind == Windows::ApplicationModel::Activation::ActivationKind::Launch)
    {
        Windows::ApplicationModel::Activation::LaunchActivatedEventArgs ^launchArgs = (Windows::ApplicationModel::Activation::LaunchActivatedEventArgs ^) args;
        Platform::String ^argumentString = launchArgs->Arguments;
        LPCWSTR rawArgumentString = argumentString->Data();
        int rawArgumentStringLength = argumentString->Length();

        if (rawArgumentString == NULL || wcslen(rawArgumentString) == 0)
        {
            mCommandLine = ReadArgsFromFile();
        }
        else
        {
            mCommandLine = ConvertLaunchArgsToMultibyte(rawArgumentString, rawArgumentStringLength);
        }
    }
    Windows::UI::Core::CoreWindow::GetForCurrentThread()->Activate();
}

void ApplicationView::Load(Platform::String^ entryPoint)
{

}

// Called by the system after initialization is complete. This implements the traditional game loop.
void ApplicationView::Run()
{
    using namespace EA::EAMain;
    std::unique_ptr<IWinRTRunner> winRTRunner(CreateWinRTRunner());

    CommandLine commandline(mCommandLine, CommandLine::FLAG_NO_PROGRAM_NAME);

    winRTRunner->Run(commandline.Argc(),commandline.Argv());
    do
    {
        // ProcessEvents will throw if the process is exiting, allowing us to
        // break out of the loop.  This will be cleaned up when we get proper
        // process lifetime management online in a future release.
        Windows::UI::Core::CoreWindow::GetForCurrentThread()->Dispatcher->ProcessEvents(Windows::UI::Core::CoreProcessEventsOption::ProcessAllIfPresent);
    } while(!winRTRunner->IsFinished());

    winRTRunner->ReportResult();
    Windows::ApplicationModel::Core::CoreApplication::Exit();

    free(mCommandLine);
    mCommandLine = nullptr;
}


void ApplicationView::Uninitialize()
{

}

namespace Internal
{
    EAMAIN_API void StartWinRtApplication()
    {
        // To do: store args so they can be passed to EAEntryPointMain.
        Windows::ApplicationModel::Core::CoreApplication::Run(ref new ApplicationViewSource());
    }
} // namespace Internal
} // namespace EAMain
} // namespace EA

#endif
