// Copyright (c) Microsoft. All rights reserved.
//
// The MIT License (MIT)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files(the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions :
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.

#include "MediaStreamSink.hpp"
#include "MFIncludes.hpp"

using namespace Media;
using namespace Microsoft::WRL;
using namespace Platform;
using namespace Windows::Foundation;

MediaStreamSink::MediaStreamSink(
    __in const MW::ComPtr<IMFMediaSink>& sink,
    __in DWORD id,
    __in const MW::ComPtr<IMFMediaType>& mt,
    __in MediaSampleHandler^ sampleHandler
    )
    : _shutdown(false)
    , _id(-1)
    , _width(0)
    , _height(0)
{
    CHK(MFCreateEventQueue(&_eventQueue));
    CHK(MFCreateMediaType(&_curMT));

    _UpdateMediaType(mt);

    _sink = sink;
    _id = id;
    _sampleHandler = sampleHandler;
}

HRESULT MediaStreamSink::GetMediaSink(__deref_out IMFMediaSink **sink)
{
    return ExceptionBoundary([this, sink]()
    {
        auto lock = _lock.LockExclusive();

        CHKNULL(sink);
        *sink = nullptr;

        _VerifyNotShutdown();

        CHK(_sink.CopyTo(sink));
    });
}

HRESULT MediaStreamSink::GetIdentifier(__out DWORD *identifier)
{
    return ExceptionBoundary([this, identifier]()
    {
        auto lock = _lock.LockExclusive();

        CHKNULL(identifier);

        _VerifyNotShutdown();

        *identifier = _id;
    });
}

HRESULT MediaStreamSink::GetMediaTypeHandler(__deref_out IMFMediaTypeHandler **handler)
{
    return ExceptionBoundary([this, handler]()
    {
        auto lock = _lock.LockExclusive();

        CHKNULL(handler);
        *handler = nullptr;

        _VerifyNotShutdown();

        *handler = this;
        this->AddRef();

    });
}

void MediaStreamSink::RequestSample()
{
    auto lock = _lock.LockExclusive();

    _VerifyNotShutdown();

    CHK(_eventQueue->QueueEventParamVar(MEStreamSinkRequestSample, GUID_NULL, S_OK, nullptr));
}

HRESULT MediaStreamSink::ProcessSample(__in_opt IMFSample *sample)
{
    return ExceptionBoundary([this, sample]()
    {
        MediaSampleHandler^ sampleHandler;
        auto mediaSample = ref new MediaSample();

        {
            auto lock = _lock.LockExclusive();

            _VerifyNotShutdown();

            if (sample == nullptr)
            {
                return;
            }

            mediaSample->Sample = sample;
            sampleHandler = _sampleHandler;
        }

        // Call back without the lock taken to avoid deadlocks
        sampleHandler(mediaSample);
    });
}

HRESULT MediaStreamSink::PlaceMarker(__in MFSTREAMSINK_MARKER_TYPE /*markerType*/, __in const PROPVARIANT * /*markerValue*/, __in const PROPVARIANT * contextValue)
{
    return ExceptionBoundary([this, contextValue]()
    {
        auto lock = _lock.LockExclusive();
        CHKNULL(contextValue);

        _VerifyNotShutdown();

        CHK(_eventQueue->QueueEventParamVar(MEStreamSinkMarker, GUID_NULL, S_OK, contextValue));
    });
}

HRESULT MediaStreamSink::Flush()
{
    return ExceptionBoundary([this]()
    {
        auto lock = _lock.LockExclusive();

        _VerifyNotShutdown();
    });
}

HRESULT MediaStreamSink::GetEvent(__in DWORD flags, __deref_out IMFMediaEvent **event)
{
    return ExceptionBoundary([this, flags, event]()
    {
        CHKNULL(event);
        *event = nullptr;

        ComPtr<IMFMediaEventQueue> eventQueue;

        {
            auto lock = _lock.LockExclusive();

            _VerifyNotShutdown();

            eventQueue = _eventQueue;
        }

        // May block for a while
        CHK(eventQueue->GetEvent(flags, event));
    });
}

HRESULT MediaStreamSink::BeginGetEvent(__in IMFAsyncCallback *callback, __in_opt IUnknown *state)
{
    return ExceptionBoundary([this, callback, state]()
    {
        auto lock = _lock.LockExclusive();

        _VerifyNotShutdown();

        CHK(_eventQueue->BeginGetEvent(callback, state));
    });
}


HRESULT MediaStreamSink::EndGetEvent(__in IMFAsyncResult *result, __deref_out IMFMediaEvent **event)
{
    return ExceptionBoundary([this, result, event]()
    {
        auto lock = _lock.LockExclusive();

        CHKNULL(event);
        *event = nullptr;

        _VerifyNotShutdown();

        CHK(_eventQueue->EndGetEvent(result, event));
    });
}

HRESULT MediaStreamSink::QueueEvent(
    __in MediaEventType met,
    __in REFGUID extendedType,
    __in HRESULT status,
    __in_opt const PROPVARIANT *value
    )
{
    return ExceptionBoundary([this, met, extendedType, status, value]()
    {
        auto lock = _lock.LockExclusive();

        _VerifyNotShutdown();

        CHK(_eventQueue->QueueEventParamVar(met, extendedType, status, value));
    });
}

HRESULT MediaStreamSink::IsMediaTypeSupported(__in IMFMediaType *mediaType, __deref_out_opt  IMFMediaType **closestMediaType)
{
    bool supported = false;

    HRESULT hr = ExceptionBoundary([this, mediaType, closestMediaType, &supported]()
    {
        auto lock = _lock.LockExclusive();

        if (closestMediaType != nullptr)
        {
            *closestMediaType = nullptr;
        }

        CHKNULL(mediaType);

        _VerifyNotShutdown();

        supported = _IsMediaTypeSupported(mediaType);
    });

    // Avoid throwing an exception to return MF_E_INVALIDMEDIATYPE as this is not a exceptional case
    return FAILED(hr) ? hr : supported ? S_OK : MF_E_INVALIDMEDIATYPE;
}

HRESULT MediaStreamSink::GetMediaTypeCount(__out DWORD *typeCount)
{
    return ExceptionBoundary([this, typeCount]()
    {
        auto lock = _lock.LockExclusive();

        CHKNULL(typeCount);

        _VerifyNotShutdown();

        // No media type provided by default (app needs to specify it)
        *typeCount = 0;
    });
}

HRESULT MediaStreamSink::GetMediaTypeByIndex(__in DWORD /*index*/, __deref_out  IMFMediaType **mediaType)
{
    HRESULT hr = ExceptionBoundary([this, mediaType]()
    {
        auto lock = _lock.LockExclusive();

        CHKNULL(mediaType);
        *mediaType = nullptr;

        _VerifyNotShutdown();
    });

    // Avoid throwing an exception to return MF_E_NO_MORE_TYPES as this is not a exceptional case
    return FAILED(hr) ? hr : MF_E_NO_MORE_TYPES;
}

HRESULT MediaStreamSink::SetCurrentMediaType(__in IMFMediaType *mediaType)
{
    return ExceptionBoundary([this, mediaType]()
    {
        auto lock = _lock.LockExclusive();

        CHKNULL(mediaType);

        _VerifyNotShutdown();

        if (!_IsMediaTypeSupported(mediaType))
        {
            CHK(MF_E_INVALIDMEDIATYPE);
        }

        _UpdateMediaType(mediaType);
    });
}

HRESULT MediaStreamSink::GetCurrentMediaType(__deref_out_opt IMFMediaType **mediaType)
{
    return ExceptionBoundary([this, mediaType]()
    {
        auto lock = _lock.LockExclusive();

        CHKNULL(mediaType);
        *mediaType = nullptr;

        _VerifyNotShutdown();

        ComPtr<IMFMediaType> mt;
        CHK(MFCreateMediaType(&mt));
        CHK(_curMT->CopyAllItems(mt.Get()));
        *mediaType = mt.Detach();
    });
}

HRESULT MediaStreamSink::GetMajorType(__out GUID *majorType)
{
    return ExceptionBoundary([this, majorType]()
    {
        auto lock = _lock.LockExclusive();

        CHKNULL(majorType);

        _VerifyNotShutdown();

        *majorType = _majorType;
    });
}

void MediaStreamSink::InternalSetCurrentMediaType(__in const ComPtr<IMFMediaType>& mediaType)
{
    auto lock = _lock.LockExclusive();

    CHKNULL(mediaType);

    _VerifyNotShutdown();

    _UpdateMediaType(mediaType);
}

void MediaStreamSink::Shutdown()
{
    auto lock = _lock.LockExclusive();

    if (_shutdown)
    {
        return;
    }
    _shutdown = true;

    (void)_eventQueue->Shutdown();
    _eventQueue = nullptr;

    _curMT = nullptr;
    _sink = nullptr;
    _sampleHandler = nullptr;
}

bool MediaStreamSink::_IsMediaTypeSupported(__in const ComPtr<IMFMediaType>& mt) const
{
    GUID majorType;
    GUID subType;
    if (SUCCEEDED(mt->GetGUID(MF_MT_MAJOR_TYPE, &majorType)) &&
        SUCCEEDED(mt->GetGUID(MF_MT_SUBTYPE, &subType)) &&
        (majorType == _majorType) &&
        (subType == _subType))
    {
        return true;
    }

    return false;
}

void MediaStreamSink::_UpdateMediaType(__in const ComPtr<IMFMediaType>& mt)
{
    CHK(mt->GetGUID(MF_MT_MAJOR_TYPE, &_majorType));
    CHK(mt->GetGUID(MF_MT_SUBTYPE, &_subType));

    if (_majorType == MFMediaType_Video)
    {
        CHK(MFGetAttributeSize(mt.Get(), MF_MT_FRAME_SIZE, &_width, &_height));
    }

    CHK(mt->CopyAllItems(_curMT.Get()));
}