#include "pch.h"
#include "Direct3DInterop.h"
#include "Direct3DContentProvider.h"
#include <windows.storage.streams.h>
#include <wrl.h>
#include <robuffer.h>
#include <opencv2\core.hpp>
#include <opencv2\imgproc.hpp>
#include <opencv2\features2d.hpp>
#include <algorithm>

using namespace Windows::Storage::Streams;
using namespace Microsoft::WRL;
using namespace Windows::Foundation;
using namespace Windows::UI::Core;
using namespace Microsoft::WRL;
using namespace Windows::Phone::Graphics::Interop;
using namespace Windows::Phone::Input::Interop;
using namespace Windows::Foundation;
using namespace Windows::Foundation::Collections;
using namespace Windows::Phone::Media::Capture;

#if !defined(_M_ARM)
#pragma message("warning: Direct3DInterop.cpp: Windows Phone camera code does not run in the emulator.")
#pragma message("warning: Direct3DInterop.cpp: Please compile as an ARM build and run on a device.")
#endif

namespace PhoneXamlDirect3DApp1Comp
{
    // Called each time a preview frame is available
    void CameraCapturePreviewSink::OnFrameAvailable(
        DXGI_FORMAT format,
        UINT width,
        UINT height,
        BYTE* pixels
        )
    {
        m_Direct3dInterop->UpdateFrame(pixels, width, height);
    }

    // Called each time a captured frame is available
    void CameraCaptureSampleSink::OnSampleAvailable(
        ULONGLONG hnsPresentationTime,
        ULONGLONG hnsSampleDuration,
        DWORD cbSample,
        BYTE* pSample)
    {


    }

    Direct3DInterop::Direct3DInterop()
        : m_algorithm(OCVFilterType::ePreview)
        , m_contentDirty(false)
        , m_backFrame(nullptr)
        , m_frontFrame(nullptr)
    {
    }

    bool Direct3DInterop::SwapFrames()
    {
        std::lock_guard<std::mutex> lock(m_mutex);
        if(m_backFrame != nullptr)
        {
            std::swap(m_backFrame, m_frontFrame);
            return true;
        }
        return false;
    }

    void Direct3DInterop::UpdateFrame(byte* buffer,int width,int height)
    {
        std::lock_guard<std::mutex> lock(m_mutex);
        if(m_backFrame == nullptr)
        {
            m_backFrame = std::shared_ptr<cv::Mat> (new cv::Mat(height, width, CV_8UC4));
            m_frontFrame = std::shared_ptr<cv::Mat> (new cv::Mat(height, width, CV_8UC4));
        }

        memcpy(m_backFrame.get()->data, buffer, 4 * height*width);
        m_contentDirty = true;
        RequestAdditionalFrame();
    }

    void Direct3DInterop::ProcessFrame()
    {
        if (SwapFrames())
        {
            if (m_renderer)
            {
                cv::Mat* mat = m_frontFrame.get();

                switch (m_algorithm)
                {
                    case OCVFilterType::ePreview:
                    {
                        break;
                    }

                    case OCVFilterType::eGray:
                    {
                        ApplyGrayFilter(mat);
                        break;
                    }

                    case OCVFilterType::eCanny:
                    {
                        ApplyCannyFilter(mat);
                        break;
                    }

                    case OCVFilterType::eBlur:
                    {
                        ApplyBlurFilter(mat);
                        break;
                    }

                    case OCVFilterType::eFindFeatures:
                    {
                        ApplyFindFeaturesFilter(mat);
                        break;
                    }

                    case OCVFilterType::eSepia:
                    {
                        ApplySepiaFilter(mat);
                        break;
                    }
                }

                m_renderer->CreateTextureFromByte(mat->data, mat->cols, mat->rows);
            }
        }
    }

    void Direct3DInterop::ApplyGrayFilter(cv::Mat* mat)
    {
        cv::Mat intermediateMat;
        cv::cvtColor(*mat, intermediateMat, COLOR_RGBA2GRAY);
        cv::cvtColor(intermediateMat, *mat, COLOR_GRAY2BGRA);
    }

    void Direct3DInterop::ApplyCannyFilter(cv::Mat* mat)
    {
        cv::Mat intermediateMat;
        cv::Canny(*mat, intermediateMat, 80, 90);
        cv::cvtColor(intermediateMat, *mat, COLOR_GRAY2BGRA);
    }

    void Direct3DInterop::ApplyBlurFilter(cv::Mat* mat)
    {
        cv::Mat intermediateMat;
        //	cv::Blur(image, intermediateMat, 80, 90);
        cv::cvtColor(intermediateMat, *mat, COLOR_GRAY2BGRA);
    }

    void Direct3DInterop::ApplyFindFeaturesFilter(cv::Mat* mat)
    {
        cv::Mat intermediateMat;
        cv::Ptr<cv::FastFeatureDetector> detector = cv::FastFeatureDetector::create(50);
        std::vector<cv::KeyPoint> features;

        cv::cvtColor(*mat, intermediateMat, COLOR_RGBA2GRAY);
        detector->detect(intermediateMat, features);

        for( unsigned int i = 0; i < std::min(features.size(), (size_t)50); i++ )
        {
            const cv::KeyPoint& kp = features[i];
            cv::circle(*mat, cv::Point((int)kp.pt.x, (int)kp.pt.y), 10, cv::Scalar(255,0,0,255));
        }
    }

    void Direct3DInterop::ApplySepiaFilter(cv::Mat* mat)
    {
        const float SepiaKernelData[16] =
        {
            /* B */0.131f, 0.534f, 0.272f, 0.f,
            /* G */0.168f, 0.686f, 0.349f, 0.f,
            /* R */0.189f, 0.769f, 0.393f, 0.f,
            /* A */0.000f, 0.000f, 0.000f, 1.f
        };

        const cv::Mat SepiaKernel(4, 4, CV_32FC1, (void*)SepiaKernelData);
        cv::transform(*mat, *mat, SepiaKernel);
    }

    IDrawingSurfaceContentProvider^ Direct3DInterop::CreateContentProvider()
    {
        ComPtr<Direct3DContentProvider> provider = Make<Direct3DContentProvider>(this);
        return reinterpret_cast<IDrawingSurfaceContentProvider^>(provider.Detach());
    }

    // IDrawingSurfaceManipulationHandler
    void Direct3DInterop::SetManipulationHost(DrawingSurfaceManipulationHost^ manipulationHost)
    {
        manipulationHost->PointerPressed +=
            ref new TypedEventHandler<DrawingSurfaceManipulationHost^, PointerEventArgs^>(this, &Direct3DInterop::OnPointerPressed);

        manipulationHost->PointerMoved +=
            ref new TypedEventHandler<DrawingSurfaceManipulationHost^, PointerEventArgs^>(this, &Direct3DInterop::OnPointerMoved);

        manipulationHost->PointerReleased +=
            ref new TypedEventHandler<DrawingSurfaceManipulationHost^, PointerEventArgs^>(this, &Direct3DInterop::OnPointerReleased);
    }

    void Direct3DInterop::RenderResolution::set(Windows::Foundation::Size renderResolution)
    {
        if (renderResolution.Width  != m_renderResolution.Width ||
            renderResolution.Height != m_renderResolution.Height)
        {
            m_renderResolution = renderResolution;

            if (m_renderer)
            {
                m_renderer->UpdateForRenderResolutionChange(m_renderResolution.Width, m_renderResolution.Height);
                RecreateSynchronizedTexture();
            }
        }
    }

    // Event Handlers

    void Direct3DInterop::OnPointerPressed(DrawingSurfaceManipulationHost^ sender, PointerEventArgs^ args)
    {
        // Insert your code here.
    }

    void Direct3DInterop::OnPointerMoved(DrawingSurfaceManipulationHost^ sender, PointerEventArgs^ args)
    {
        // Insert your code here.
    }

    void Direct3DInterop::OnPointerReleased(DrawingSurfaceManipulationHost^ sender, PointerEventArgs^ args)
    {
        // Insert your code here.
    }

    void Direct3DInterop::StartCamera()
    {
        // Set the capture dimensions
        Size captureDimensions;
        captureDimensions.Width = 640;
        captureDimensions.Height = 480;

        // Open the AudioVideoCaptureDevice for video only
        IAsyncOperation<AudioVideoCaptureDevice^> ^openOperation = AudioVideoCaptureDevice::OpenForVideoOnlyAsync(CameraSensorLocation::Back, captureDimensions);

        openOperation->Completed = ref new AsyncOperationCompletedHandler<AudioVideoCaptureDevice^>(
            [this] (IAsyncOperation<AudioVideoCaptureDevice^> ^operation, Windows::Foundation::AsyncStatus status)
            {
                if (status == Windows::Foundation::AsyncStatus::Completed)
                {
                    auto captureDevice = operation->GetResults();

                    // Save the reference to the opened video capture device
                    pAudioVideoCaptureDevice = captureDevice;

                    // Retrieve the native ICameraCaptureDeviceNative interface from the managed video capture device
                    ICameraCaptureDeviceNative *iCameraCaptureDeviceNative = NULL;
                    HRESULT hr = reinterpret_cast<IUnknown*>(captureDevice)->QueryInterface(__uuidof(ICameraCaptureDeviceNative), (void**) &iCameraCaptureDeviceNative);

                    // Save the pointer to the native interface
                    pCameraCaptureDeviceNative = iCameraCaptureDeviceNative;

                    // Initialize the preview dimensions (see the accompanying article at )
                    // The aspect ratio of the capture and preview resolution must be equal,
                    // 4:3 for capture => 4:3 for preview, and 16:9 for capture => 16:9 for preview.
                    Size previewDimensions;
                    previewDimensions.Width = 640;
                    previewDimensions.Height = 480;

                    IAsyncAction^ setPreviewResolutionAction = pAudioVideoCaptureDevice->SetPreviewResolutionAsync(previewDimensions);
                    setPreviewResolutionAction->Completed = ref new AsyncActionCompletedHandler(
                        [this](IAsyncAction^ action, Windows::Foundation::AsyncStatus status)
                        {
                            HResult hr = action->ErrorCode;

                            if (status == Windows::Foundation::AsyncStatus::Completed)
                            {
                                // Create the sink
                                MakeAndInitialize<CameraCapturePreviewSink>(&pCameraCapturePreviewSink);
                                pCameraCapturePreviewSink->SetDelegate(this);
                                pCameraCaptureDeviceNative->SetPreviewSink(pCameraCapturePreviewSink);

                                // Set the preview format
                                pCameraCaptureDeviceNative->SetPreviewFormat(DXGI_FORMAT::DXGI_FORMAT_B8G8R8A8_UNORM);
                            }
                        }
                    );

                    // Retrieve IAudioVideoCaptureDeviceNative native interface from managed projection.
                    IAudioVideoCaptureDeviceNative *iAudioVideoCaptureDeviceNative = NULL;
                    hr = reinterpret_cast<IUnknown*>(captureDevice)->QueryInterface(__uuidof(IAudioVideoCaptureDeviceNative), (void**) &iAudioVideoCaptureDeviceNative);

                    // Save the pointer to the IAudioVideoCaptureDeviceNative native interface
                    pAudioVideoCaptureDeviceNative = iAudioVideoCaptureDeviceNative;

                    // Set sample encoding format to ARGB. See the documentation for further values.
                    pAudioVideoCaptureDevice->VideoEncodingFormat = CameraCaptureVideoFormat::Argb;

                    // Initialize and set the CameraCaptureSampleSink class as sink for captures samples
                    MakeAndInitialize<CameraCaptureSampleSink>(&pCameraCaptureSampleSink);
                    pAudioVideoCaptureDeviceNative->SetVideoSampleSink(pCameraCaptureSampleSink);

                    // Start recording (only way to receive samples using the ICameraCaptureSampleSink interface
                    pAudioVideoCaptureDevice->StartRecordingToSinkAsync();
                }
            }
        );

    }
    // Interface With Direct3DContentProvider
    HRESULT Direct3DInterop::Connect(_In_ IDrawingSurfaceRuntimeHostNative* host)
    {
        m_renderer = ref new QuadRenderer();
        m_renderer->Initialize();
        m_renderer->UpdateForWindowSizeChange(WindowBounds.Width, WindowBounds.Height);
        m_renderer->UpdateForRenderResolutionChange(m_renderResolution.Width, m_renderResolution.Height);
        StartCamera();

        return S_OK;
    }

    void Direct3DInterop::Disconnect()
    {
        m_renderer = nullptr;
    }

    HRESULT Direct3DInterop::PrepareResources(_In_ const LARGE_INTEGER* presentTargetTime, _Out_ BOOL* contentDirty)
    {
        *contentDirty = m_contentDirty;
        if(m_contentDirty)
        {
            ProcessFrame();
        }
        m_contentDirty = false;
        return S_OK;
    }

    HRESULT Direct3DInterop::GetTexture(_In_ const DrawingSurfaceSizeF* size, _Out_ IDrawingSurfaceSynchronizedTextureNative** synchronizedTexture, _Out_ DrawingSurfaceRectF* textureSubRectangle)
    {
        m_renderer->Update();
        m_renderer->Render();
        return S_OK;
    }

    ID3D11Texture2D* Direct3DInterop::GetTexture()
    {
        return m_renderer->GetTexture();
    }
}
