#include "games/iidx/local_camera.h"

#include "util/logging.h"
#include "util/utils.h"

std::string CAMERA_CONTROL_LABELS[] = {
    "Pan",
    "Tilt",
    "Roll",
    "Zoom",
    "Exposure",
    "Iris",
    "Focus"
};

std::string DRAW_MODE_LABELS[] = {
    "Stretch",
    "Crop",
    "Letterbox",
    "Crop to 4:3",
    "Letterbox to 4:3",
};

// static HRESULT printTextureLevelDesc(LPDIRECT3DTEXTURE9 texture) {
//     HRESULT hr = S_OK;
//     D3DSURFACE_DESC desc;
//     hr = texture->GetLevelDesc(0, &desc);
//     log_info("iidx::tdjcam", "Texture Desc Size: {}x{} Res Type: {} Format: {} Usage: {}", desc.Width, desc.Height, (int) desc.Type, (int) desc.Format, (int) desc.Usage);
//     return hr;
// }

LONG TARGET_SURFACE_WIDTH = 1280;
LONG TARGET_SURFACE_HEIGHT = 720;

double RATIO_16_9 = 16.0 / 9.0;
double RATIO_4_3 = 4.0 / 3.0;

namespace games::iidx {

    IIDXLocalCamera::IIDXLocalCamera(
        std::string name,
        BOOL prefer_16_by_9,
        IMFActivate *pActivate,
        IDirect3DDeviceManager9 *pD3DManager,
        LPDIRECT3DDEVICE9EX device,
        LPDIRECT3DTEXTURE9 *texture_target
    ):
        m_nRefCount(1),
        m_name(name),
        m_prefer_16_by_9(prefer_16_by_9),
        m_device(device),
        m_texture_target(texture_target),
        m_texture_original(*texture_target)
    {
        InitializeCriticalSection(&m_critsec);

        HRESULT hr = S_OK;
        IMFAttributes *pAttributes = nullptr;

        EnterCriticalSection(&m_critsec);

        log_info("iidx::tdjcam", "[{}] Creating camera", m_name);

        // Retrive symlink of Camera for control configurations
        hr = pActivate->GetAllocatedString(
            MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE_VIDCAP_SYMBOLIC_LINK,
            &m_pwszSymbolicLink,
            &m_cchSymbolicLink
        );
        if (FAILED(hr)) { goto done; }
        log_misc("iidx::tdjcam", "[{}] Symlink: {}", m_name, GetSymLink());

        // Create the media source object.
        hr = pActivate->ActivateObject(IID_PPV_ARGS(&m_pSource));
        if (FAILED(hr)) { goto done; }

        // Retain reference to the camera
        m_pSource->AddRef();
        log_misc("iidx::tdjcam", "[{}] Activated", m_name);

        // Create an attribute store to hold initialization settings.
        hr = MFCreateAttributes(&pAttributes, 2);
        if (FAILED(hr)) { goto done; }

        hr = pAttributes->SetUnknown(MF_SOURCE_READER_D3D_MANAGER, pD3DManager);
        if (FAILED(hr)) { goto done; }

        hr = pAttributes->SetUINT32(MF_SOURCE_READER_DISABLE_DXVA, FALSE);
        if (FAILED(hr)) { goto done; }

        // TODO: Color space conversion
        // if (SUCCEEDED(hr)) {
        //     hr = pAttributes->SetUINT32(MF_SOURCE_READER_ENABLE_ADVANCED_VIDEO_PROCESSING, TRUE);
        // }       
        // if (SUCCEEDED(hr)) {
		// 	hr = pAttributes->SetUINT32(MF_READWRITE_DISABLE_CONVERTERS, FALSE);
        // }

        // Create the source reader.
        hr = MFCreateSourceReaderFromMediaSource(
            m_pSource,
            pAttributes,
            &m_pSourceReader
        );
        if (FAILED(hr)) { goto done; }
        log_misc("iidx::tdjcam", "[{}] Created source reader", m_name);

        hr = InitTargetTexture();
        if (FAILED(hr)) { goto done; }

        // Camera should be still usable even if camera control is not supported
        InitCameraControl();

    done:
        if (SUCCEEDED(hr)) {
            m_initialized = true;
            log_misc("iidx::tdjcam", "[{}] Initialized", m_name);
        } else {
            log_warning("iidx::tdjcam", "[{}] Failed to create camera: {}", m_name, hr);
        }
        SafeRelease(&pAttributes);
        LeaveCriticalSection(&m_critsec);
    }

    HRESULT IIDXLocalCamera::StartCapture() {
        HRESULT hr = S_OK;
        IMFMediaType *pType = nullptr;

        if (!m_initialized) {
            log_warning("iidx::tdjcam", "[{}] Camera not initialized", m_name);
            return E_FAIL;
        }

        // Try to find a suitable output type.
        log_misc("iidx::tdjcam", "[{}] Find best media type", m_name);
        UINT32 bestWidth = 0;
        double bestFrameRate = 0;

        // The loop should terminate by MF_E_NO_MORE_TYPES
        // Adding a hard limit just in case
        for (DWORD i = 0; i < 1000; i++) {
            hr = m_pSourceReader->GetNativeMediaType(
                (DWORD)MF_SOURCE_READER_FIRST_VIDEO_STREAM,
                i,
                &pType
                );

            if (FAILED(hr)) { 
                if (hr != MF_E_NO_MORE_TYPES) {
                    log_warning("iidx::tdjcam", "[{}] Cannot get media type {} {}", m_name, i, hr);
                }
                break; 
            }

            hr = TryMediaType(pType, &bestWidth, &bestFrameRate);
            if (SUCCEEDED(hr)) {
                MediaTypeInfo info = GetMediaTypeInfo(pType);
                m_mediaTypeInfos.push_back(info);
                if (hr == S_OK) {
                    m_pAutoMediaType = pType;
                }
            } else {
                // Invalid media type (e.g. no conversion function)
                SafeRelease(&pType);
            }
        }

        // Sort available media types
        std::sort(m_mediaTypeInfos.begin(), m_mediaTypeInfos.end(), [](const MediaTypeInfo &a, const MediaTypeInfo &b) {
            if (a.width != b.width) {
                return a.width > b.width;
            }
            if (a.height != b.height) {
                return a.height > b.height;
            }
            if (a.frameRate != b.frameRate) {
                return (int)a.frameRate > (int)b.frameRate;
            }
            return a.subtype.Data1 > b.subtype.Data1;
        });

        if (!m_pAutoMediaType) {
            m_pAutoMediaType = m_mediaTypeInfos.front().p_mediaType;
        }

        IMFMediaType *pSelectedMediaType = nullptr;

        // Find media type specified by user configurations
        if (!m_useAutoMediaType && m_selectedMediaTypeDescription.length() > 0) {
            log_info("iidx::tdjcam", "[{}] Use media type from config {}", m_name, m_selectedMediaTypeDescription);
            auto it = std::find_if(m_mediaTypeInfos.begin(), m_mediaTypeInfos.end(), [this](const MediaTypeInfo &item){
                return item.description.compare(this->m_selectedMediaTypeDescription) == 0;
            });
            if (it != m_mediaTypeInfos.end()) {
                pSelectedMediaType = (*it).p_mediaType;
            }
        }

        hr = S_OK;

        if (!pSelectedMediaType) {
            pSelectedMediaType = m_pAutoMediaType;
        }

        if (SUCCEEDED(hr)) { 
            hr = ChangeMediaType(pSelectedMediaType);
        }

        if (SUCCEEDED(hr)) {
            log_info("iidx::tdjcam", "[{}] Creating thread", m_name);
            CreateThread();
        }
        return hr;
    }

    HRESULT IIDXLocalCamera::ChangeMediaType(IMFMediaType *pType) {
        HRESULT hr = S_OK;
        MediaTypeInfo info = GetMediaTypeInfo(pType);
        log_info("iidx::tdjcam", "[{}] Changing media type: {}", m_name, info.description);

        auto it = std::find_if(m_mediaTypeInfos.begin(), m_mediaTypeInfos.end(), [pType](const MediaTypeInfo &item) {
            return item.p_mediaType == pType;
        });
        m_selectedMediaTypeIndex = it - m_mediaTypeInfos.begin();
        m_selectedMediaTypeDescription = info.description;

        if (SUCCEEDED(hr)) {
            hr = m_pSourceReader->SetCurrentMediaType(
                (DWORD)MF_SOURCE_READER_FIRST_VIDEO_STREAM,
                NULL,
                pType
                );
        }

        if (SUCCEEDED(hr)) {
            m_cameraWidth = info.width;
            m_cameraHeight = info.height;
            UpdateDrawRect();
        }

        return hr;
    }

    void IIDXLocalCamera::UpdateDrawRect() {
        double cameraRatio = (double)m_cameraWidth / m_cameraHeight;

        RECT cameraRect = {0, 0, m_cameraWidth, m_cameraHeight};
        RECT targetRect = {0, 0, TARGET_SURFACE_WIDTH, TARGET_SURFACE_HEIGHT};

        switch (m_drawMode) {
            case DrawModeStretch: {
                CopyRect(&m_rcSource, &cameraRect);
                CopyRect(&m_rcDest, &targetRect);
                break;
            }
            case DrawModeCrop: {
                if (cameraRatio > RATIO_16_9) {
                    // take full source height, crop left/right
                    LONG croppedWidth = m_cameraHeight * RATIO_16_9;
                    m_rcSource.left = (LONG)(m_cameraWidth - croppedWidth) / 2;
                    m_rcSource.top = 0;
                    m_rcSource.right = m_rcSource.left + croppedWidth;
                    m_rcSource.bottom = m_cameraHeight;
                } else {
                    // take full source width, crop top/bottom
                    LONG croppedHeight = m_cameraWidth / RATIO_16_9;
                    m_rcSource.left = 0;
                    m_rcSource.top = (LONG)(m_cameraHeight - croppedHeight) / 2;
                    m_rcSource.right = m_cameraWidth;
                    m_rcSource.bottom = m_rcSource.top + croppedHeight;
                }
                CopyRect(&m_rcDest, &targetRect);
                break;
            }
            case DrawModeLetterbox: {
                CopyRect(&m_rcSource, &cameraRect);
                if (cameraRatio > RATIO_16_9) {
                    // take full dest width, empty top/bottom
                    LONG boxedHeight = TARGET_SURFACE_WIDTH / cameraRatio;
                    m_rcDest.left = 0;
                    m_rcDest.top = (LONG)(TARGET_SURFACE_HEIGHT - boxedHeight) / 2;
                    m_rcDest.right = TARGET_SURFACE_WIDTH;
                    m_rcDest.bottom = m_rcDest.top + boxedHeight;
                } else {
                    // take full dest height, empty top/bottom
                    LONG boxedWidth = TARGET_SURFACE_HEIGHT * cameraRatio;
                    m_rcDest.left = (LONG)(TARGET_SURFACE_WIDTH - boxedWidth) / 2;
                    m_rcDest.top = 0;
                    m_rcDest.right = m_rcDest.left + boxedWidth;
                    m_rcDest.bottom = TARGET_SURFACE_HEIGHT;
                }
                break;
            }
            case DrawModeCrop4_3: {
                if (cameraRatio > RATIO_4_3) {
                    // take full source height, crop left/right
                    LONG croppedWidth = m_cameraHeight * RATIO_4_3;
                    m_rcSource.left = (LONG)(m_cameraWidth - croppedWidth) / 2;
                    m_rcSource.top = 0;
                    m_rcSource.right = m_rcSource.left + croppedWidth;
                    m_rcSource.bottom = m_cameraHeight;
                } else {
                    // take full source width, crop top/bottom
                    LONG croppedHeight = m_cameraWidth / RATIO_4_3;
                    m_rcSource.left = 0;
                    m_rcSource.top = (LONG)(m_cameraHeight - croppedHeight) / 2;
                    m_rcSource.right = m_cameraWidth;
                    m_rcSource.bottom = m_rcSource.top + croppedHeight;
                }
                CopyRect(&m_rcDest, &targetRect);
                break;
            }
            case DrawModeLetterbox4_3: {
                CopyRect(&m_rcSource, &cameraRect);
                if (cameraRatio > RATIO_4_3) {
                    // take full dest width, empty top/bottom
                    LONG boxedHeight = TARGET_SURFACE_HEIGHT / RATIO_4_3;
                    m_rcDest.left = 0;
                    m_rcDest.top = (LONG)(TARGET_SURFACE_HEIGHT - boxedHeight) / 2;
                    m_rcDest.right = TARGET_SURFACE_WIDTH;
                    m_rcDest.bottom = m_rcDest.top + boxedHeight;
                } else {
                    // take full dest height, empty top/bottom
                    LONG boxedWidth = TARGET_SURFACE_WIDTH * RATIO_4_3;
                    m_rcDest.left = (LONG)(TARGET_SURFACE_WIDTH - boxedWidth) / 2;
                    m_rcDest.top = 0;
                    m_rcDest.right = m_rcDest.left + boxedWidth;
                    m_rcDest.bottom = TARGET_SURFACE_HEIGHT;
                }
                break;
            }
        }

        // ensure the rects are valid
        IntersectRect(&m_rcSource, &m_rcSource, &cameraRect);
        IntersectRect(&m_rcDest, &m_rcDest, &targetRect);

        log_info(
            "iidx::tdjcam", "[{}] Update draw rect mode={} src=({}, {}, {}, {}) dest=({}, {}, {}, {})",
            m_name,
            DRAW_MODE_LABELS[m_drawMode],
            m_rcSource.left,
            m_rcSource.top,
            m_rcSource.right,
            m_rcSource.bottom,
            m_rcDest.left,
            m_rcDest.top,
            m_rcDest.right,
            m_rcDest.bottom
        );

        m_device->ColorFill(m_pDestSurf, &targetRect, D3DCOLOR_XRGB(0, 0, 0));
    }
  
    void IIDXLocalCamera::CreateThread() {
        // Create thread
        m_drawThread = new std::thread([this]() {
            double accumulator = 0.0;
            while (this->m_active) {
                this->Render();
                double frameTimeMicroSec = (1000000.0 / this->m_frameRate);
                int floorFrameTimeMicroSec = floor(frameTimeMicroSec);
                // This maybe an overkill but who knows
                accumulator += (frameTimeMicroSec - floorFrameTimeMicroSec);
                if (accumulator > 1.0) {
                    accumulator -= 1.0;
                    floorFrameTimeMicroSec += 1;
                }                
                std::this_thread::sleep_for(std::chrono::microseconds(floorFrameTimeMicroSec));
            }
        });
    }

    LPDIRECT3DTEXTURE9 IIDXLocalCamera::GetTexture() {
        return m_texture;
    }

    IAMCameraControl* IIDXLocalCamera::GetCameraControl() {
        return m_pCameraControl;
    }

    HRESULT IIDXLocalCamera::InitCameraControl() {
        HRESULT hr = S_OK;

        log_misc("iidx::tdjcam", "[{}] Init camera control", m_name);

        hr = m_pSource->QueryInterface(IID_IAMCameraControl, (void**)&m_pCameraControl); 
        if (FAILED(hr)) { 
            // The device does not support IAMCameraControl 
            log_warning("iidx::tdjcam", "[{}] Camera control not supported", m_name);
            return E_FAIL;
        } 

        for (size_t i = 0; i < CAMERA_CONTROL_PROP_SIZE; i++) {
            long minValue = 0;
            long maxValue = 0;
            long delta = 0;
            long defaultValue = 0;
            long defFlags = 0;
            long value = 0;
            long valueFlags = 0;

            m_pCameraControl->GetRange(
                i,
                &minValue,
                &maxValue,
                &delta,
                &defaultValue,
                &defFlags
            );
            m_pCameraControl->Get(
                i,
                &value,
                &valueFlags
            );
            m_controlProps.push_back({
                minValue,
                maxValue,
                delta,
                defaultValue,
                defFlags,
                value,
                valueFlags,
            });

            CameraControlProp prop = m_controlProps.at(i);

            log_misc(
                "iidx::tdjcam", "[{}] >> {} range=({}, {}) default={} delta={} dFlags={} value={} vFlags={}", 
                m_name,
                CAMERA_CONTROL_LABELS[i], 
                prop.minValue, prop.maxValue,
                prop.defaultValue,
                prop.delta,
                prop.defFlags,
                prop.value,
                prop.valueFlags
            );
        }

        m_controlOptionsInitialized = true;

        return hr;
    }

    HRESULT IIDXLocalCamera::GetCameraControlProp(int index, CameraControlProp *pProp) {
        if (!m_controlOptionsInitialized) {
            return E_FAIL;
        }

        auto targetProp = m_controlProps.at(index);

        pProp->minValue = targetProp.minValue;
        pProp->maxValue = targetProp.maxValue;
        pProp->defaultValue = targetProp.defaultValue;
        pProp->delta = targetProp.delta;
        pProp->defFlags = targetProp.defFlags;
        pProp->value = targetProp.value;
        pProp->valueFlags = targetProp.valueFlags;

        return S_OK;
    }

    HRESULT IIDXLocalCamera::SetCameraControlProp(int index, long value, long flags) {
        if (!m_controlOptionsInitialized || !m_allowManualControl) {
            return E_FAIL;
        }

        if (index < 0 || index >= CAMERA_CONTROL_PROP_SIZE) {
            return E_INVALIDARG;
        }

        auto targetProp = &(m_controlProps.at(index));
        HRESULT hr = m_pCameraControl->Set(index, value, flags);
        if (SUCCEEDED(hr)) {
            m_pCameraControl->Get(
                index,
                &targetProp->value,
                &targetProp->valueFlags
            );
        }

        return hr;
    }

    HRESULT IIDXLocalCamera::ResetCameraControlProps() {
        log_info("iidx::tdjcam", "[{}] Reset camera control", m_name);
        for (size_t i = 0; i < CAMERA_CONTROL_PROP_SIZE; i++) {
            CameraControlProp prop = m_controlProps.at(i);
            SetCameraControlProp(i, prop.defaultValue, prop.defFlags);
        }
        return S_OK;
    }

    std::string IIDXLocalCamera::GetName() {
        return m_name;
    }

    std::string IIDXLocalCamera::GetSymLink() {
        if (!m_pwszSymbolicLink) {
            return "(unknown)";
        }
        return ws2s(m_pwszSymbolicLink);
    }

    MediaTypeInfo IIDXLocalCamera::GetMediaTypeInfo(IMFMediaType *pType) {
        MediaTypeInfo info = {};
        HRESULT hr = S_OK;
        MFRatio frameRate = { 0, 0 };

        info.p_mediaType = pType;

        // Find the video subtype.
        hr = pType->GetGUID(MF_MT_SUBTYPE, &info.subtype);
        if (FAILED(hr)) { goto done; }

        // Get the frame size.
        hr = MFGetAttributeSize(pType, MF_MT_FRAME_SIZE, &info.width, &info.height);
        if (FAILED(hr)) { goto done; }

        // Get frame rate
        hr = MFGetAttributeRatio(
            pType, 
            MF_MT_FRAME_RATE, 
            (UINT32*)&frameRate.Numerator, 
            (UINT32*)&frameRate.Denominator
        );
        if (FAILED(hr)) { goto done; }

        info.frameRate = frameRate.Numerator / frameRate.Denominator;

        info.description = fmt::format(
            "{}x{} @{}FPS {}", 
            info.width,
            info.height,
            (int)info.frameRate,
            GetVideoFormatName(info.subtype)
        );
    done:
        return info;
    }

    std::string IIDXLocalCamera::GetVideoFormatName(GUID subtype) {
        if (subtype == MFVideoFormat_YUY2) {
            return "YUY2";
        }

        if (subtype == MFVideoFormat_NV12) {
            return "NV12";
        }

        if (subtype == MFVideoFormat_MJPG) {
            return "MJPG";
        }

        return "Unknown";
    }

    /** 
     * Return values:
     *   S_OK:      this is a "better" media type than the existing one
     *   S_FALSE:   valid media type, but not "better"
     *   E_*:       invalid meia type
     */
    HRESULT IIDXLocalCamera::TryMediaType(IMFMediaType *pType, UINT32 *pBestWidth, double *pBestFrameRate) {
        HRESULT hr = S_OK;
        UINT32 width = 0, height = 0;
        GUID subtype = { 0, 0, 0, 0 };
        MFRatio frameRate = { 0, 0 };

        hr = pType->GetGUID(MF_MT_SUBTYPE, &subtype);

        if (FAILED(hr))  { 
            log_warning("iidx::tdjcam", "[{}] Failed to get subtype: {}", m_name, hr);
            return hr;    
        }

        hr = MFGetAttributeSize(pType, MF_MT_FRAME_SIZE, &width, &height);
        if (FAILED(hr))  { 
            log_warning("iidx::tdjcam", "[{}] Failed to get frame size: {}", m_name, hr);
            return hr;
        }
        
        // Only support format with converters
        // TODO: verify conversion support with DXVA
        if (subtype != MFVideoFormat_YUY2 && subtype != MFVideoFormat_NV12) {
            return E_FAIL;
        }

        // Frame rate
        hr = MFGetAttributeRatio(
            pType, 
            MF_MT_FRAME_RATE, 
            (UINT32*)&frameRate.Numerator, 
            (UINT32*)&frameRate.Denominator
        );
        if (FAILED(hr))  { 
            log_warning("iidx::tdjcam", "[{}] Failed to get frame rate: {}", m_name, hr);
            return hr;
        }
        double frameRateValue = frameRate.Numerator / frameRate.Denominator;

        // Filter by aspect ratio
        auto aspect_ratio = 4.f / 3.f;
        if (m_prefer_16_by_9) {
            aspect_ratio = 16.f / 9.f;
        }
        if (fabs((height * aspect_ratio) - width) > 0.01f) {
            return S_FALSE;
        }

        // If we have 1280x720 already, only try for better frame rate
        if ((*pBestWidth >= (UINT32)TARGET_SURFACE_WIDTH) && (width > *pBestWidth) && (frameRateValue < *pBestFrameRate)) {
            return S_FALSE;
        }

        // Check if this format has better resolution / frame rate
        if ((width > *pBestWidth) || (width >= (UINT32)TARGET_SURFACE_WIDTH && frameRateValue >= *pBestFrameRate)) {
            // log_misc(
            //     "iidx::tdjcam", "Better media type {} ({}x{}) @({} FPS)", 
            //     GetVideoFormatName(subtype),
            //     width,
            //     height,
            //     (int)frameRateValue
            // );

            *pBestWidth = width;
            *pBestFrameRate = frameRateValue;
            return S_OK;
        }

        return S_FALSE;
    }

    HRESULT IIDXLocalCamera::InitTargetTexture() {
        HRESULT hr = S_OK;

        // Create a new destination texture
        hr = m_device->CreateTexture(TARGET_SURFACE_WIDTH, TARGET_SURFACE_HEIGHT, 1, D3DUSAGE_RENDERTARGET, D3DFMT_X8R8G8B8, D3DPOOL_DEFAULT, &m_texture, NULL);
        if (FAILED(hr)) { goto done; }

        // Create a D3D9 surface for the destination texture so that camera sample can be drawn onto it
        hr = m_texture->GetSurfaceLevel(0, &m_pDestSurf);
        if (FAILED(hr)) { goto done; }

        // Make the game use this new texture as camera stream source
        *m_texture_target = m_texture;
        m_active = TRUE;

        // printTextureLevelDesc(m_texture);
        // printTextureLevelDesc(m_texture_original);

    done:
        if (SUCCEEDED(hr)) {
            log_misc("iidx::tdjcam", "[{}] Created texture", m_name);
        } else {
            log_warning("iidx::tdjcam", "[{}] Failed to create texture: {}", m_name, hr);
        }
        return hr;
    }

    HRESULT IIDXLocalCamera::DrawSample(IMFMediaBuffer *pSrcBuffer) {
        if (!m_active) {
            return E_FAIL;
        }

        EnterCriticalSection(&m_critsec);

        HRESULT hr = S_OK;
        IDirect3DSurface9 *pCameraSurf = NULL;
        IDirect3DQuery9* pEventQuery = nullptr;

        hr = MFGetService(pSrcBuffer, MR_BUFFER_SERVICE, IID_PPV_ARGS(&pCameraSurf));

        // Stretch camera texture to destination
        hr = m_device->StretchRect(pCameraSurf, &m_rcSource, m_pDestSurf, &m_rcDest, D3DTEXF_LINEAR);
        if (FAILED(hr)) { goto done; }

        // It is necessary to flush the command queue 
        // or the data is not ready for the receiver to read.
        // Adapted from : https://msdn.microsoft.com/en-us/library/windows/desktop/bb172234%28v=vs.85%29.aspx
        // Also see : http://www.ogre3d.org/forums/viewtopic.php?f=5&t=50486
        m_device->CreateQuery(D3DQUERYTYPE_EVENT, &pEventQuery);
        if (pEventQuery) {
            pEventQuery->Issue(D3DISSUE_END);
            while (S_FALSE == pEventQuery->GetData(NULL, 0, D3DGETDATA_FLUSH));
            pEventQuery->Release(); // Must be released or causes a leak and reference count increment
        }

    done:
        if (FAILED(hr)) {
            log_warning("iidx::tdjcam", "Error in DrawSample {}", GetLastError());
        }
        SafeRelease(&pCameraSurf);
        LeaveCriticalSection(&m_critsec);
        return hr;
    }

    HRESULT IIDXLocalCamera::ReadSample() {
        HRESULT hr;
        DWORD streamIndex, flags;
        LONGLONG llTimeStamp;
        IMFSample *pSample = nullptr;
        IMFMediaBuffer *pBuffer = nullptr;

        hr = m_pSourceReader->ReadSample(
            MF_SOURCE_READER_FIRST_VIDEO_STREAM,
            0,
            &streamIndex,
            &flags,
            &llTimeStamp,
            &pSample
        );

        if (pSample) {
            // Draw to D3D
            pSample->GetBufferByIndex(0, &pBuffer);
            hr = DrawSample(pBuffer);
        }

        SafeRelease(&pBuffer);
        SafeRelease(&pSample);

        return hr;
    }

    LPDIRECT3DTEXTURE9 IIDXLocalCamera::Render() {
        if (!m_active) {
            return nullptr;
        }
        HRESULT hr = ReadSample();
        if (FAILED(hr)) {
            return nullptr;
        }
        return m_texture;
    }

    ULONG IIDXLocalCamera::Release() {
        log_info("iidx::tdjcam", "[{}] Release camera", m_name);
        m_active = false;

        ULONG uCount = InterlockedDecrement(&m_nRefCount);

        for (size_t i = 0; i < m_mediaTypeInfos.size(); i++) {
            SafeRelease(&(m_mediaTypeInfos.at(i).p_mediaType));
        }

        SafeRelease(&m_pDestSurf);

        if (m_pSource) {
            m_pSource->Shutdown();
            m_pSource->Release();
        }

        CoTaskMemFree(m_pwszSymbolicLink);
        m_pwszSymbolicLink = NULL;
        m_cchSymbolicLink = 0;

        if (uCount == 0) {
            delete this;
        }
        // For thread safety, return a temporary variable.
        return uCount;
    }
}