/*
 * Copyright (C) 2014 Alex Christensen <achristensen@webkit.org>
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE INC. OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#pragma once

#include "COMPtr.h"
#include "MediaPlayerPrivate.h"

#include <D3D9.h>
#include <Dxva2api.h>

#include <Mfapi.h>
#include <Mferror.h>
#include <Mfidl.h>
#include <evcode.h>
#include <evr.h>

#include <wtf/Deque.h>
#include <wtf/Lock.h>
#include <wtf/ThreadingPrimitives.h>
#include <wtf/WeakPtr.h>
#include <wtf/win/Win32Handle.h>

namespace WebCore {

class MediaPlayerPrivateMediaFoundation : public MediaPlayerPrivateInterface, public CanMakeWeakPtr<MediaPlayerPrivateMediaFoundation> {
public:
    explicit MediaPlayerPrivateMediaFoundation(MediaPlayer*);
    ~MediaPlayerPrivateMediaFoundation();
    static void registerMediaEngine(MediaEngineRegistrar);

    static void getSupportedTypes(HashSet<String, ASCIICaseInsensitiveHash>& types);
    static MediaPlayer::SupportsType supportsType(const MediaEngineSupportParameters&);
    static bool isAvailable();

    void load(const String& url) override;
    void cancelLoad() override;

    void play() override;
    void pause() override;

    bool supportsFullscreen() const override;

    FloatSize naturalSize() const override;

    bool hasVideo() const override;
    bool hasAudio() const override;

    void setVisible(bool) override;

    bool seeking() const override;
    void seek(float) override;

    void setRate(float) override;

    float duration() const override;

    float currentTime() const override;

    bool paused() const override;

    void setVolume(float) override;

    bool supportsMuting() const override;
    void setMuted(bool) override;

    MediaPlayer::NetworkState networkState() const override;
    MediaPlayer::ReadyState readyState() const override;

    float maxTimeSeekable() const override;

    std::unique_ptr<PlatformTimeRanges> buffered() const override;

    bool didLoadingProgress() const override;

    void setSize(const IntSize&) override;

    void paint(GraphicsContext&, const FloatRect&) override;

private:
    WeakPtr<MediaPlayerPrivateMediaFoundation> m_weakThis;
    MediaPlayer* m_player;
    IntSize m_size;
    bool m_visible;
    bool m_loadingProgress;
    bool m_paused;
    bool m_hasAudio;
    bool m_hasVideo;
    bool m_preparingToPlay;
    float m_volume;
    MediaPlayer::NetworkState m_networkState;
    MediaPlayer::ReadyState m_readyState;

    class MediaPlayerListener;
    HashSet<MediaPlayerListener*> m_listeners;
    Lock m_mutexListeners;

    FloatSize m_cachedNaturalSize;
    mutable Lock m_cachedNaturalSizeLock;

    COMPtr<IMFMediaSession> m_mediaSession;
    COMPtr<IMFSourceResolver> m_sourceResolver;
    COMPtr<IMFMediaSource> m_mediaSource;
    COMPtr<IMFTopology> m_topology;
    COMPtr<IMFPresentationDescriptor> m_sourcePD;
    COMPtr<IMFVideoDisplayControl> m_videoDisplay;

    bool createSession();
    bool startSession();
    bool endSession();
    bool startCreateMediaSource(const String& url);
    bool endCreatedMediaSource(IMFAsyncResult*);
    bool endGetEvent(IMFAsyncResult*);
    bool createTopologyFromSource();
    bool addBranchToPartialTopology(int stream);
    bool createOutputNode(COMPtr<IMFStreamDescriptor> sourceSD, COMPtr<IMFTopologyNode>&);
    bool createSourceStreamNode(COMPtr<IMFStreamDescriptor> sourceSD, COMPtr<IMFTopologyNode>&);

    void updateReadyState();

    COMPtr<IMFVideoDisplayControl> videoDisplay();

    void onCreatedMediaSource();
    void onTopologySet();
    void onBufferingStarted();
    void onBufferingStopped();
    void onSessionStarted();
    void onSessionEnded();

    HWND hostWindow();
    void invalidateFrameView();

    void addListener(MediaPlayerListener*);
    void removeListener(MediaPlayerListener*);
    void setNaturalSize(const FloatSize&);
    void notifyDeleted();

    bool setAllChannelVolumes(float);

    class MediaPlayerListener {
    public:
        MediaPlayerListener() = default;
        virtual ~MediaPlayerListener() = default;

        virtual void onMediaPlayerDeleted() { }
    };

    class AsyncCallback : public IMFAsyncCallback, public MediaPlayerListener {
    public:
        AsyncCallback(MediaPlayerPrivateMediaFoundation*, bool event);
        ~AsyncCallback();

        HRESULT STDMETHODCALLTYPE QueryInterface(_In_ REFIID riid, __RPC__deref_out void __RPC_FAR *__RPC_FAR *ppvObject) override;
        ULONG STDMETHODCALLTYPE AddRef() override;
        ULONG STDMETHODCALLTYPE Release() override;

        HRESULT STDMETHODCALLTYPE GetParameters(__RPC__out DWORD *pdwFlags, __RPC__out DWORD *pdwQueue) override;
        HRESULT STDMETHODCALLTYPE Invoke(__RPC__in_opt IMFAsyncResult *pAsyncResult) override;

        void onMediaPlayerDeleted() override;

    private:
        ULONG m_refCount;
        MediaPlayerPrivateMediaFoundation* m_mediaPlayer;
        bool m_event;
        Lock m_mutex;
    };

    typedef Deque<COMPtr<IMFSample>> VideoSampleList;

    class VideoSamplePool {
    public:
        VideoSamplePool() = default;
        virtual ~VideoSamplePool() = default;

        HRESULT initialize(VideoSampleList& samples);
        void clear();

        HRESULT getSample(COMPtr<IMFSample>&);
        HRESULT returnSample(IMFSample*);
        bool areSamplesPending();

    private:
        Lock m_lock;
        VideoSampleList m_videoSampleQueue;
        bool m_initialized { false };
        unsigned m_pending { 0 };
    };

    class Direct3DPresenter;

    class VideoScheduler {
    public:
        VideoScheduler() = default;
        virtual ~VideoScheduler() = default;

        void setPresenter(Direct3DPresenter* presenter) { m_presenter = presenter; }

        void setFrameRate(const MFRatio& fps);
        void setClockRate(float rate) { m_playbackRate = rate; }

        const LONGLONG& lastSampleTime() const { return m_lastSampleTime; }
        const LONGLONG& frameDuration() const { return m_frameDuration; }

        HRESULT startScheduler(IMFClock*);
        HRESULT stopScheduler();

        HRESULT scheduleSample(IMFSample*, bool presentNow);
        HRESULT processSamplesInQueue(LONG& nextSleep);
        HRESULT processSample(IMFSample*, LONG& nextSleep);
        HRESULT flush();

    private:
        static DWORD WINAPI schedulerThreadProc(LPVOID lpParameter);
        DWORD schedulerThreadProcPrivate();

        Deque<COMPtr<IMFSample>> m_scheduledSamples;
        Lock m_lock;

        COMPtr<IMFClock> m_clock;
        Direct3DPresenter* m_presenter { nullptr };

        DWORD m_threadID { 0 };
        WTF::Win32Handle m_schedulerThread;
        WTF::Win32Handle m_threadReadyEvent;
        WTF::Win32Handle m_flushEvent;

        float m_playbackRate { 1.0f };
        MFTIME m_frameDuration { 0 };
        MFTIME m_lastSampleTime { 0 };

        std::atomic<bool> m_exitThread { false };

        void stopThread() { m_exitThread = true; }
    };

    class Direct3DPresenter {
    public:
        Direct3DPresenter();
        ~Direct3DPresenter();

        enum DeviceState {
            DeviceOK,
            DeviceReset,
            DeviceRemoved,
        };

        // Returns the IDirect3DDeviceManager9 interface.
        HRESULT getService(REFGUID guidService, REFIID riid, void** ppv);

        HRESULT checkFormat(D3DFORMAT);

        HRESULT setVideoWindow(HWND);
        HWND getVideoWindow() const { return m_hwnd; }
        HRESULT setDestinationRect(const RECT& destRect);
        RECT getDestinationRect() const { return m_destRect; };

        HRESULT createVideoSamples(IMFMediaType* format, VideoSampleList& videoSampleQueue);
        void releaseResources();

        HRESULT checkDeviceState(DeviceState&);
        HRESULT presentSample(IMFSample*, LONGLONG target);

        UINT refreshRate() const { return m_displayMode.RefreshRate; }

        void paintCurrentFrame(GraphicsContext&, const FloatRect&);

    private:
        HRESULT initializeD3D();
        HRESULT getSwapChainPresentParameters(IMFMediaType*, D3DPRESENT_PARAMETERS* presentParams);
        HRESULT createD3DDevice();
        HRESULT createD3DSample(IDirect3DSwapChain9*, COMPtr<IMFSample>& videoSample);

        UINT m_deviceResetToken { 0 };
        HWND m_hwnd { nullptr };
        RECT m_destRect;
        D3DDISPLAYMODE m_displayMode;

        Lock m_lock;
        
        COMPtr<IDirect3D9Ex> m_direct3D9;
        COMPtr<IDirect3DDevice9Ex> m_device;
        COMPtr<IDirect3DDeviceManager9> m_deviceManager;
        COMPtr<IDirect3DSurface9> m_surfaceRepaint;

        COMPtr<IDirect3DSurface9> m_memSurface;
        int m_width { 0 };
        int m_height { 0 };
    };

    class CustomVideoPresenter
        : public IMFVideoPresenter
        , public IMFVideoDeviceID
        , public IMFTopologyServiceLookupClient
        , public IMFGetService
        , public IMFActivate
        , public IMFVideoDisplayControl
        , public IMFAsyncCallback
        , public MediaPlayerListener {
    public:
        CustomVideoPresenter(MediaPlayerPrivateMediaFoundation*);
        ~CustomVideoPresenter();

        HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, __RPC__deref_out void __RPC_FAR *__RPC_FAR *ppvObject) override;
        ULONG STDMETHODCALLTYPE AddRef() override;
        ULONG STDMETHODCALLTYPE Release() override;

        // IMFClockStateSink
        HRESULT STDMETHODCALLTYPE OnClockStart(MFTIME hnsSystemTime, LONGLONG llClockStartOffset) override;
        HRESULT STDMETHODCALLTYPE OnClockStop(MFTIME hnsSystemTime) override;
        HRESULT STDMETHODCALLTYPE OnClockPause(MFTIME hnsSystemTime) override;
        HRESULT STDMETHODCALLTYPE OnClockRestart(MFTIME hnsSystemTime) override;
        HRESULT STDMETHODCALLTYPE OnClockSetRate(MFTIME hnsSystemTime, float flRate) override;

        // IMFVideoPresenter
        HRESULT STDMETHODCALLTYPE ProcessMessage(MFVP_MESSAGE_TYPE eMessage, ULONG_PTR ulParam) override;
        HRESULT STDMETHODCALLTYPE GetCurrentMediaType(_Outptr_  IMFVideoMediaType **ppMediaType) override;

        // IMFVideoDeviceID
        HRESULT STDMETHODCALLTYPE GetDeviceID(IID* pDeviceID) override;

        // IMFTopologyServiceLookupClient
        HRESULT STDMETHODCALLTYPE InitServicePointers(_In_  IMFTopologyServiceLookup *pLookup) override;
        HRESULT STDMETHODCALLTYPE ReleaseServicePointers(void) override;

        // IMFGetService
        HRESULT STDMETHODCALLTYPE GetService(REFGUID guidService, REFIID riid, LPVOID *ppvObject) override;

        // IMFActivate
        HRESULT STDMETHODCALLTYPE ActivateObject(REFIID riid, void **ppv) override;
        HRESULT STDMETHODCALLTYPE DetachObject() override;
        HRESULT STDMETHODCALLTYPE ShutdownObject() override;

        // IMFAttributes
        HRESULT STDMETHODCALLTYPE GetItem(__RPC__in REFGUID guidKey, __RPC__inout_opt PROPVARIANT *pValue) override { return E_NOTIMPL; }
        HRESULT STDMETHODCALLTYPE GetItemType(__RPC__in REFGUID guidKey, __RPC__out MF_ATTRIBUTE_TYPE *pType) override { return E_NOTIMPL; }
        HRESULT STDMETHODCALLTYPE CompareItem(__RPC__in REFGUID guidKey, __RPC__in REFPROPVARIANT Value, __RPC__out BOOL *pbResult) override { return E_NOTIMPL; }
        HRESULT STDMETHODCALLTYPE Compare(__RPC__in_opt IMFAttributes *pTheirs, MF_ATTRIBUTES_MATCH_TYPE MatchType, __RPC__out BOOL *pbResult) override { return E_NOTIMPL; }
        HRESULT STDMETHODCALLTYPE GetUINT32(__RPC__in REFGUID guidKey, __RPC__out UINT32 *punValue) override { return E_NOTIMPL; }
        HRESULT STDMETHODCALLTYPE GetUINT64(__RPC__in REFGUID guidKey, __RPC__out UINT64 *punValue) override { return E_NOTIMPL; }
        HRESULT STDMETHODCALLTYPE GetDouble(__RPC__in REFGUID guidKey, __RPC__out double *pfValue) override { return E_NOTIMPL; }
        HRESULT STDMETHODCALLTYPE GetGUID(__RPC__in REFGUID guidKey, __RPC__out GUID *pguidValue) override { return E_NOTIMPL; }
        HRESULT STDMETHODCALLTYPE GetStringLength(__RPC__in REFGUID guidKey, __RPC__out UINT32 *pcchLength) override { return E_NOTIMPL; }
        HRESULT STDMETHODCALLTYPE GetString(__RPC__in REFGUID guidKey, __RPC__out_ecount_full(cchBufSize) LPWSTR pwszValue, UINT32 cchBufSize, __RPC__inout_opt UINT32 *pcchLength) override { return E_NOTIMPL; }
        HRESULT STDMETHODCALLTYPE GetAllocatedString(__RPC__in REFGUID guidKey, __RPC__deref_out_ecount_full_opt((*pcchLength + 1)) LPWSTR *ppwszValue, __RPC__out UINT32 *pcchLength) override { return E_NOTIMPL; }
        HRESULT STDMETHODCALLTYPE GetBlobSize(__RPC__in REFGUID guidKey, __RPC__out UINT32 *pcbBlobSize) override { return E_NOTIMPL; }
        HRESULT STDMETHODCALLTYPE GetBlob(__RPC__in REFGUID guidKey, __RPC__out_ecount_full(cbBufSize) UINT8 *pBuf, UINT32 cbBufSize, __RPC__inout_opt UINT32 *pcbBlobSize) override { return E_NOTIMPL; }
        HRESULT STDMETHODCALLTYPE GetAllocatedBlob(__RPC__in REFGUID guidKey, __RPC__deref_out_ecount_full_opt(*pcbSize) UINT8 **ppBuf, __RPC__out UINT32 *pcbSize) override { return E_NOTIMPL; }
        HRESULT STDMETHODCALLTYPE GetUnknown(__RPC__in REFGUID guidKey, __RPC__in REFIID riid, __RPC__deref_out_opt LPVOID *ppv) override { return E_NOTIMPL; }
        HRESULT STDMETHODCALLTYPE SetItem(__RPC__in REFGUID guidKey, __RPC__in REFPROPVARIANT Value) override { return E_NOTIMPL; }
        HRESULT STDMETHODCALLTYPE DeleteItem(__RPC__in REFGUID guidKey) override { return E_NOTIMPL; }
        HRESULT STDMETHODCALLTYPE DeleteAllItems(void) override { return E_NOTIMPL; }
        HRESULT STDMETHODCALLTYPE SetUINT32(__RPC__in REFGUID guidKey, UINT32 unValue) override { return E_NOTIMPL; }
        HRESULT STDMETHODCALLTYPE SetUINT64(__RPC__in REFGUID guidKey, UINT64 unValue) override { return E_NOTIMPL; }
        HRESULT STDMETHODCALLTYPE SetDouble(__RPC__in REFGUID guidKey, double fValue) override { return E_NOTIMPL; }
        HRESULT STDMETHODCALLTYPE SetGUID(__RPC__in REFGUID guidKey, __RPC__in REFGUID guidValue) override { return E_NOTIMPL; }
        HRESULT STDMETHODCALLTYPE SetString(__RPC__in REFGUID guidKey, __RPC__in_string LPCWSTR wszValue) override { return E_NOTIMPL; }
        HRESULT STDMETHODCALLTYPE SetBlob(__RPC__in REFGUID guidKey, __RPC__in_ecount_full(cbBufSize) const UINT8 *pBuf, UINT32 cbBufSize) override { return E_NOTIMPL; }
        HRESULT STDMETHODCALLTYPE SetUnknown(__RPC__in REFGUID guidKey, __RPC__in_opt IUnknown *pUnknown) override { return E_NOTIMPL; }
        HRESULT STDMETHODCALLTYPE LockStore(void) override { return E_NOTIMPL; }
        HRESULT STDMETHODCALLTYPE UnlockStore(void) override { return E_NOTIMPL; }
        HRESULT STDMETHODCALLTYPE GetCount(__RPC__out UINT32 *pcItems) override { return E_NOTIMPL; }
        HRESULT STDMETHODCALLTYPE GetItemByIndex(UINT32 unIndex, __RPC__out GUID *pguidKey, __RPC__inout_opt PROPVARIANT *pValue) override { return E_NOTIMPL; }
        HRESULT STDMETHODCALLTYPE CopyAllItems(__RPC__in_opt IMFAttributes *pDest) override { return E_NOTIMPL; }

        // IMFVideoDisplayControl
        HRESULT STDMETHODCALLTYPE GetNativeVideoSize(SIZE* pszVideo, SIZE* pszARVideo) override { return E_NOTIMPL; }
        HRESULT STDMETHODCALLTYPE GetIdealVideoSize(SIZE* pszMin, SIZE* pszMax) override { return E_NOTIMPL; }
        HRESULT STDMETHODCALLTYPE SetVideoPosition(const MFVideoNormalizedRect* pnrcSource, const LPRECT prcDest) override;
        HRESULT STDMETHODCALLTYPE GetVideoPosition(MFVideoNormalizedRect* pnrcSource, LPRECT prcDest) override;
        HRESULT STDMETHODCALLTYPE SetAspectRatioMode(DWORD dwAspectRatioMode) override { return E_NOTIMPL; }
        HRESULT STDMETHODCALLTYPE GetAspectRatioMode(DWORD* pdwAspectRatioMode) override { return E_NOTIMPL; }
        HRESULT STDMETHODCALLTYPE SetVideoWindow(HWND hwndVideo) override;
        HRESULT STDMETHODCALLTYPE GetVideoWindow(HWND* phwndVideo) override;
        HRESULT STDMETHODCALLTYPE RepaintVideo() override;
        HRESULT STDMETHODCALLTYPE GetCurrentImage(BITMAPINFOHEADER* pBih, BYTE** pDib, DWORD* pcbDib, LONGLONG* pTimeStamp) override { return E_NOTIMPL; }
        HRESULT STDMETHODCALLTYPE SetBorderColor(COLORREF Clr) override { return E_NOTIMPL; }
        HRESULT STDMETHODCALLTYPE GetBorderColor(COLORREF* pClr) override { return E_NOTIMPL; }
        HRESULT STDMETHODCALLTYPE SetRenderingPrefs(DWORD dwRenderFlags) override { return E_NOTIMPL; }
        HRESULT STDMETHODCALLTYPE GetRenderingPrefs(DWORD* pdwRenderFlags) override { return E_NOTIMPL; }
        HRESULT STDMETHODCALLTYPE SetFullscreen(BOOL bFullscreen) override { return E_NOTIMPL; }
        HRESULT STDMETHODCALLTYPE GetFullscreen(BOOL* pbFullscreen) override { return E_NOTIMPL; }

        // IMFAsyncCallback methods
        HRESULT STDMETHODCALLTYPE GetParameters(DWORD*, DWORD*) override { return E_NOTIMPL; }
        HRESULT STDMETHODCALLTYPE Invoke(IMFAsyncResult* pAsyncResult) override;

        // MediaPlayerListener
        void onMediaPlayerDeleted() override;

        void paintCurrentFrame(GraphicsContext&, const FloatRect&);

        float currentTime();

        float maxTimeLoaded() const { return m_maxTimeLoaded; }

    private:
        ULONG m_refCount { 0 };
        Lock m_lock;
        MediaPlayerPrivateMediaFoundation* m_mediaPlayer;

        enum RenderState {
            RenderStateStarted = 1,
            RenderStateStopped,
            RenderStatePaused,
            RenderStateShutdown,
        };

        RenderState m_renderState { RenderStateShutdown };
        COMPtr<IMFClock> m_clock;
        COMPtr<IMediaEventSink> m_mediaEventSink;
        COMPtr<IMFTransform> m_mixer;
        COMPtr<IMFMediaType> m_mediaType;
        std::unique_ptr<Direct3DPresenter> m_presenterEngine;
        MFVideoNormalizedRect m_sourceRect;
        bool m_sampleNotify { false };
        bool m_prerolled { false };
        bool m_repaint { false };
        bool m_endStreaming { false };
        VideoScheduler m_scheduler;
        VideoSamplePool m_samplePool;
        unsigned m_tokenCounter { 0 };
        float m_rate { 1.0f };
        float m_maxTimeLoaded { 0.0f };

        bool isActive() const;

        bool isScrubbing() const { return m_rate == 0.0f; }

        HRESULT configureMixer(IMFTransform* mixer);
        HRESULT flush();
        HRESULT setMediaType(IMFMediaType*);
        HRESULT checkShutdown() const;
        HRESULT renegotiateMediaType();
        HRESULT processInputNotify();
        HRESULT beginStreaming();
        HRESULT endStreaming();
        HRESULT checkEndOfStream();
        HRESULT isMediaTypeSupported(IMFMediaType*);
        HRESULT createOptimalVideoType(IMFMediaType* proposedType, IMFMediaType** optimalType);
        HRESULT calculateOutputRectangle(IMFMediaType* proposedType, RECT& outputRect);

        void processOutputLoop();
        HRESULT processOutput();
        HRESULT deliverSample(IMFSample*, bool repaint);
        HRESULT trackSample(IMFSample*);
        void releaseResources();

        HRESULT onSampleFree(IMFAsyncResult*);

        void notifyEvent(long EventCode, LONG_PTR Param1, LONG_PTR Param2);
    };

    COMPtr<CustomVideoPresenter> m_presenter;
};

}
