#pragma once

#include <d3d9.h>
#include <dxva2api.h>
#include <mutex>
#include <mfapi.h>
#include <mfidl.h>
#include <mferror.h>
#include <mfreadwrite.h>
#include <string>
#include <thread>
#include <strmif.h>
#include <vector>

#define CAMERA_CONTROL_PROP_SIZE 7
#define DRAW_MODE_SIZE 5

template <class T> void SafeRelease(T **ppT)
{
    if (*ppT)
    {
        (*ppT)->Release();
        *ppT = nullptr;
    }
}

typedef void (*IMAGE_TRANSFORM_FN)(
    BYTE*       pDest,
    LONG        lDestStride,
    const BYTE* pSrc,
    LONG        lSrcStride,
    DWORD       dwWidthInPixels,
    DWORD       dwHeightInPixels
);

struct CameraControlProp {
    long minValue = 0;
    long maxValue = 0;
    long delta = 0;
    long defaultValue = 0;
    long defFlags = 0;
    long value = 0;
    long valueFlags = 0;
};

struct MediaTypeInfo {
    GUID            subtype = GUID_NULL;
    UINT32          width = 0;
    UINT32          height = 0;
    double          frameRate = 0.0;
    IMFMediaType*   p_mediaType = nullptr;
    std::string     description = "";
    LONG            *plStride = nullptr;
};

typedef enum {
    DrawModeStretch = 0,
    DrawModeCrop = 1,
    DrawModeLetterbox = 2,
    DrawModeCrop4_3 = 3,
    DrawModeLetterbox4_3 = 4,
} LocalCameraDrawMode;

extern std::string CAMERA_CONTROL_LABELS[];

extern std::string DRAW_MODE_LABELS[];

namespace games::iidx {
    class IIDXLocalCamera {
    protected:
        virtual ~IIDXLocalCamera() {};

        ULONG m_nRefCount;
        CRITICAL_SECTION m_critsec;

        std::string m_name;
        BOOL m_prefer_16_by_9;
        WCHAR *m_pwszSymbolicLink = nullptr;
        UINT32 m_cchSymbolicLink = 0;

        // For reading frames from Camera
        IMFMediaSource *m_pSource = nullptr;
        IMFSourceReader *m_pSourceReader = nullptr;

        // Camera Format information
        double                  m_frameRate = 0;
        LONG                    m_cameraWidth;
        LONG                    m_cameraHeight;

        // Draw rectangles
        RECT                    m_rcSource;
        RECT                    m_rcDest;

        // Thread to draw texture asynchorously
        std::thread             *m_drawThread = nullptr;

        // DirectX9 DeviceEx
        LPDIRECT3DDEVICE9EX m_device;

        // Address to hook camera texture onto the game
        LPDIRECT3DTEXTURE9 *m_texture_target = nullptr;

        // Target texture
        LPDIRECT3DTEXTURE9 m_texture = nullptr;
        IDirect3DSurface9 *m_pDestSurf = nullptr;

        // Storing original texture for clean up
        LPDIRECT3DTEXTURE9 m_texture_original = nullptr;

        IAMCameraControl *m_pCameraControl = nullptr;

        // Camera Control
        std::vector<CameraControlProp> m_controlProps = {};

        BOOL m_controlOptionsInitialized = false;

    public:
        // True if first part of the setup steps (those in the constructor) succeeded
        BOOL m_initialized = false;

        // True if all the setup steps succeeded
        BOOL m_active = false;

        // Media type select
        std::vector<MediaTypeInfo> m_mediaTypeInfos = {};
        int m_selectedMediaTypeIndex = 0;
        bool m_useAutoMediaType = true;
        IMFMediaType *m_pAutoMediaType = nullptr;
        std::string m_selectedMediaTypeDescription = "";
        bool m_allowManualControl = false;

        LocalCameraDrawMode m_drawMode = DrawModeCrop4_3;

        IIDXLocalCamera(
            std::string name,
            BOOL prefer_16_by_9,
            IMFActivate *pActivate,
            IDirect3DDeviceManager9 *pD3DManager,
            LPDIRECT3DDEVICE9EX device,
            LPDIRECT3DTEXTURE9 *texture_target
        );
        LPDIRECT3DTEXTURE9 GetTexture();
        ULONG Release();
        IAMCameraControl* GetCameraControl();
        HRESULT GetCameraControlProp(int index, CameraControlProp *pProp);
        HRESULT SetCameraControlProp(int index, long value, long flags);
        HRESULT ResetCameraControlProps();
        std::string GetName();
        std::string GetSymLink();
        HRESULT ChangeMediaType(IMFMediaType *pType);
        HRESULT StartCapture();
        void UpdateDrawRect();

    private:
        HRESULT CreateD3DDeviceManager();
        void CreateThread();
        MediaTypeInfo GetMediaTypeInfo(IMFMediaType *pType);
        std::string GetVideoFormatName(GUID subtype);
        HRESULT TryMediaType(IMFMediaType *pType, UINT32 *pBestWidth, double *pBestFrameRate);
        HRESULT InitTargetTexture();
        HRESULT InitCameraControl();
        HRESULT DrawSample(IMFMediaBuffer *pSrcBuffer);
        HRESULT ReadSample();
        LPDIRECT3DTEXTURE9 Render();
    };
}