/* ImageSourceImpl.cpp - WinRT image wrapper used to render the maps into brushes.
* Copyright (C) 2014 Paulo Pinto
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc., 59 Temple Place - Suite 330,
* Boston, MA 02111-1307, USA.
*/

#include "pch.h"
#include "ImageSourceImpl.h"

using namespace ImageSourceImplementation;
using namespace Platform;
using namespace Microsoft::WRL;
using namespace Windows::ApplicationModel;
using namespace Windows::Graphics::Display;
using namespace Windows::UI;
using namespace Windows::UI::Xaml;

// Helper methods
// helper function to convert COM errors into WinRT exceptions
static inline void ThrowIfFailed(HRESULT hr)
{
	if (FAILED(hr)) {
		throw Platform::Exception::CreateException(hr);
	}
}

// Helper to convert WinRT colors into DirectX colors
static inline D2D1_COLOR_F ConvertToColorF(Windows::UI::Color color)
{
	return D2D1::ColorF(color.R / 255.0f, color.G / 255.0f, color.B / 255.0f, color.A / 255.0f);
}

// Helper to convert WinRT rectangles into DirectX rectangles
static inline D2D1_RECT_F ConvertToRectF(Windows::Foundation::Rect source)
{
	return D2D1::RectF(source.Left, source.Top, source.Right, source.Bottom);
}


ImageSourceImpl::ImageSourceImpl(int pixelWidth, int pixelHeight, bool isOpaque) :
    SurfaceImageSource(pixelWidth, pixelHeight, isOpaque)
{
    m_width = pixelWidth;
    m_height = pixelHeight;

    CreateDeviceResources();

    Application::Current->Suspending += ref new SuspendingEventHandler(this, &ImageSourceImpl::OnSuspending);
}

// Initialize hardware-dependent resources.
void ImageSourceImpl::CreateDeviceResources()
{
    UINT creationFlags = D3D11_CREATE_DEVICE_BGRA_SUPPORT; 

#if defined(_DEBUG)    
    // If the project is in a debug build, enable debugging via SDK Layers.
    creationFlags |= D3D11_CREATE_DEVICE_DEBUG;
#endif

    const D3D_FEATURE_LEVEL featureLevels[] =
    {
        D3D_FEATURE_LEVEL_11_1,
        D3D_FEATURE_LEVEL_11_0,
        D3D_FEATURE_LEVEL_10_1,
        D3D_FEATURE_LEVEL_10_0,
        D3D_FEATURE_LEVEL_9_3,
        D3D_FEATURE_LEVEL_9_2,
        D3D_FEATURE_LEVEL_9_1,
    };

    // Creating the device
	ThrowIfFailed(
        D3D11CreateDevice(
            nullptr,      
            D3D_DRIVER_TYPE_HARDWARE,
            nullptr,
            creationFlags,
            featureLevels,
            ARRAYSIZE(featureLevels),
            D3D11_SDK_VERSION,
            &m_d3dDevice,     
            nullptr,
            nullptr
            )
        );

    // configure the device
    ComPtr<IDXGIDevice> dxgiDevice;
	ThrowIfFailed(
        m_d3dDevice.As(&dxgiDevice)
        );

    // we need the 2D device
	ThrowIfFailed(
        D2D1CreateDevice(
            dxgiDevice.Get(),
            nullptr,
            &m_d2dDevice
            )
        );

	ThrowIfFailed(
        m_d2dDevice->CreateDeviceContext(
            D2D1_DEVICE_CONTEXT_OPTIONS_NONE,
            &m_d2dContext
            )
        );    
	
    // Query for the required ISurfaceImageSourceNative interface.
    Microsoft::WRL::ComPtr<ISurfaceImageSourceNative> sisNative;    
	ThrowIfFailed(
        reinterpret_cast<IUnknown*>(this)->QueryInterface(IID_PPV_ARGS(&sisNative))
        );

    // Associate the device with the image source.
	ThrowIfFailed(
        sisNative->SetDevice(dxgiDevice.Get())
        );
}

// Begins drawing, allowing updates to content in the specified area.
void ImageSourceImpl::BeginDraw(Windows::Foundation::Rect updateRect)
{
    POINT offset;
    ComPtr<IDXGISurface> surface;

    // Express target area as a native RECT type.
    RECT updateRectNative; 
    updateRectNative.left = static_cast<LONG>(updateRect.Left);
    updateRectNative.top = static_cast<LONG>(updateRect.Top);
    updateRectNative.right = static_cast<LONG>(updateRect.Right);
    updateRectNative.bottom = static_cast<LONG>(updateRect.Bottom);

    Microsoft::WRL::ComPtr<ISurfaceImageSourceNative> sisNative;    
	ThrowIfFailed(
        reinterpret_cast<IUnknown*>(this)->QueryInterface(IID_PPV_ARGS(&sisNative))
        );

    // Begin drawing
    HRESULT beginDrawHR = sisNative->BeginDraw(updateRectNative, &surface, &offset);
 
    if (SUCCEEDED(beginDrawHR))
    {
        ComPtr<ID2D1Bitmap1> bitmap;
		ThrowIfFailed(
            m_d2dContext->CreateBitmapFromDxgiSurface(
                surface.Get(),
                nullptr,
                &bitmap
                )
            );

		m_d2dContext->SetTarget(bitmap.Get());
        m_d2dContext->BeginDraw();
		m_d2dContext->SetTransform(D2D1::Matrix3x2F::Identity());
		m_d2dContext->Clear(D2D1::ColorF(D2D1::ColorF::White));
    }
    else if (beginDrawHR == DXGI_ERROR_DEVICE_REMOVED || beginDrawHR == DXGI_ERROR_DEVICE_RESET)
    {
        // If the device has been removed or reset, re-create it
        CreateDeviceResources();
        BeginDraw(updateRect);
    }
    else
    {
        // Something bad happened, let the user know about it
		ThrowIfFailed(beginDrawHR);
    }
}

void ImageSourceImpl::EndDraw()
{
	ThrowIfFailed(
        m_d2dContext->EndDraw()
        );

    m_d2dContext->SetTarget(nullptr);

    Microsoft::WRL::ComPtr<ISurfaceImageSourceNative> sisNative;    
	ThrowIfFailed(
        reinterpret_cast<IUnknown*>(this)->QueryInterface(IID_PPV_ARGS(&sisNative))
        );

	ThrowIfFailed(
        sisNative->EndDraw()
        );
}

// Clears the background with the given color.
void ImageSourceImpl::Clear(Windows::UI::Color color)
{
	m_d2dContext->Clear(ConvertToColorF(color));
}

// Draws a filled rectangle with the given color and position.
void ImageSourceImpl::FillSolidRect(Windows::UI::Color color, Windows::Foundation::Rect rect)
{
    // Create a solid color D2D brush.
    ComPtr<ID2D1SolidColorBrush> brush;
	ThrowIfFailed(
        m_d2dContext->CreateSolidColorBrush(
		ConvertToColorF(color),
            &brush
            )
        );

    // Draw a filled rectangle.
	m_d2dContext->FillRectangle(ConvertToRectF(rect), brush.Get());
}

void ImageSourceImpl::DrawLine(Windows::UI::Color color, float x0, float y0, float x1, float y1)
{
	ComPtr<ID2D1SolidColorBrush> brush;
	ThrowIfFailed(
		m_d2dContext->CreateSolidColorBrush(
		ConvertToColorF(color),
		&brush
		)
	);

	m_d2dContext->DrawLine(D2D1::Point2F(static_cast<FLOAT>(x0), y0),
		D2D1::Point2F(static_cast<FLOAT>(x1), y1),
		brush.Get());
}

void ImageSourceImpl::OnSuspending(Object ^sender, SuspendingEventArgs ^e)
{
    ComPtr<IDXGIDevice3> dxgiDevice;
    m_d3dDevice.As(&dxgiDevice);

	dxgiDevice->Trim();
}

