


#include "stdafx.h"
#include <windows.h>
#include <tchar.h>
#include <iostream>
#include <chrono>
#include <thread>
#include <vector>
#include <string>
#include <memory>
#include <cstring>
#include <cstdint>

#include <atlbase.h>  
#include <atlcom.h>

#include "MIDL/MFormats.h" 


#define GLEW_STATIC
#include <glew.h>
#include <GL/gl.h>
#pragma comment(lib, "opengl32.lib")
#pragma comment(lib, "glew32s.lib")

// stb_image 
// https://raw.githubusercontent.com/nothings/stb/master/stb_image.h
#ifndef STB_IMAGE_IMPLEMENTATION
#define STB_IMAGE_IMPLEMENTATION
#endif
#include "stb_image.h"

using namespace std;



// Minimal hidden GL context helper
struct GLHiddenContext {
    HWND hwnd = NULL;
    HDC hdc = NULL;
    HGLRC hglrc = NULL;

    bool Create() {
        HINSTANCE hInst = GetModuleHandle(NULL);

        // fixed simple class name (no __DATE__ concatenation which caused issues)
        const char* CLASS_NAME = "GLHiddenWindowClass";

        WNDCLASSA wc = {};
        wc.style = CS_OWNDC;
        wc.lpfnWndProc = DefWindowProcA;
        wc.hInstance = hInst;
        wc.lpszClassName = CLASS_NAME;
        RegisterClassA(&wc);

        hwnd = CreateWindowA(CLASS_NAME, "GLHidden", WS_OVERLAPPEDWINDOW, 0, 0, 32, 32, NULL, NULL, hInst, NULL);
        if (!hwnd) return false;

        hdc = GetDC(hwnd);
        if (!hdc) return false;

        PIXELFORMATDESCRIPTOR pfd = {};
        pfd.nSize = sizeof(pfd);
        pfd.nVersion = 1;
        pfd.dwFlags = PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER;
        pfd.iPixelType = PFD_TYPE_RGBA;
        pfd.cColorBits = 32;
        pfd.cAlphaBits = 8;
        pfd.cDepthBits = 24;

        int pf = ChoosePixelFormat(hdc, &pfd);
        if (!pf) return false;
        if (!SetPixelFormat(hdc, pf, &pfd)) return false;

        hglrc = wglCreateContext(hdc);
        if (!hglrc) return false;

        if (!wglMakeCurrent(hdc, hglrc)) return false;

        // initialize GLEW
        GLenum glewRes = glewInit();
        if (glewRes != GLEW_OK) {
            const char* err = reinterpret_cast<const char*>(glewGetErrorString(glewRes));
            fprintf(stderr, "glewInit failed: %s\n", err ? err : "unknown");
            // some drivers may return GL_INVALID_ENUM on glewInit but still usable; you may choose to continue.
            // return false; // <- you can make this fatal if you prefer
        }
        return true;
    }

    void Destroy() {
        if (hglrc) { wglMakeCurrent(NULL, NULL); wglDeleteContext(hglrc); hglrc = NULL; }
        if (hdc && hwnd) { ReleaseDC(hwnd, hdc); hdc = NULL; }
        if (hwnd) { DestroyWindow(hwnd); hwnd = NULL; }
    }

    ~GLHiddenContext() { Destroy(); }
};

// Create GL texture from image file (uses stb_image). Returns 0 on failure.
GLuint CreateGLTextureFromImageFile(const wchar_t* wzPath, int* outW = nullptr, int* outH = nullptr) {
    if (!wzPath) return 0;

    // Convert wchar_t path to UTF-8
    int needed = WideCharToMultiByte(CP_UTF8, 0, wzPath, -1, nullptr, 0, nullptr, nullptr);
    if (needed <= 0) return 0;
    std::string pathUtf8(needed, '\0');
    WideCharToMultiByte(CP_UTF8, 0, wzPath, -1, &pathUtf8[0], needed, nullptr, nullptr);

    int w = 0, h = 0, channels = 0;
    unsigned char* img = stbi_load(pathUtf8.c_str(), &w, &h, &channels, 4); // force RGBA
    if (!img) {
        wprintf(L"stbi_load failed for path: %ls\n", wzPath);
        return 0;
    }

    // Flip vertically: stb_image loads top-left origin; OpenGL usually expects bottom-left
    const int rowBytes = w * 4;
    std::vector<unsigned char> flipped(static_cast<size_t>(rowBytes) * h);
    for (int y = 0; y < h; ++y) {
        memcpy(&flipped[(h - 1 - y) * rowBytes], &img[y * rowBytes], rowBytes);
    }

    GLuint tex = 0;
    glGenTextures(1, &tex);
    glBindTexture(GL_TEXTURE_2D, tex);
    glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
    // internalFormat, width, height, border, format, type, data
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, w, h, 0, GL_RGBA, GL_UNSIGNED_BYTE, flipped.data());
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    glBindTexture(GL_TEXTURE_2D, 0);

    stbi_image_free(img);
    if (outW) *outW = w;
    if (outH) *outH = h;
    return tex;
}



// Entry point (wmain to accept wide file path)
int wmain(int argc, wchar_t* argv[]) {
    if (argc < 2) {
        wprintf(L"Usage: SampleFilePlayback <image.png/jpg> [signaling] [room]\n");
        return 0;
    }

    CoInitializeEx(NULL, COINIT_MULTITHREADED);

    const wchar_t* imagePath = argv[1];


    // 1) create GL context
    GLHiddenContext gl;
    if (!gl.Create()) {
        wprintf(L"Failed to create GL context.\n");
        CoUninitialize();
        return -1;
    }

    // 2) create texture from image
    int texW = 0, texH = 0;
    GLuint tex = CreateGLTextureFromImageFile(imagePath, &texW, &texH);
    if (!tex) {
        wprintf(L"Failed to create GL texture from %ls\n", imagePath);
        // cleanup
        CoUninitialize();
        return -1;
    }
    wprintf(L"Created GL texture %u (%dx%d)\n", tex, texW, texH);

    // 3) init preview
    CComPtr<IMPreview> cpPreview;
    HRESULT hr = cpPreview.CoCreateInstance(__uuidof(MFPreview));
    if (FAILED(hr)) {
        wprintf(L"Preview init failed\n");
        glDeleteTextures(1, &tex);
        CoUninitialize();
        return -1;
    }
    cpPreview->PreviewEnable(L"", 0, 1);
    cpPreview->PreviewWindowSet(L"", 0);

    // 4) get MFormats factory 
    CComPtr<IMFFactory> spFactory;
     hr = spFactory.CoCreateInstance(__uuidof(MFFactory));
    if (FAILED(hr)) {
        wprintf(L"CoCreateInstance(__uuidof(MFFactory)) returned null - check SDK initialization and include paths\n");
        glDeleteTextures(1, &tex);
        CoUninitialize();
        return -1;
    }

    // 5) get MFormats factory for OpenGL texture work
    CComPtr<IMFFactoryOpenGL> GLFactory;
     hr = GLFactory.CoCreateInstance(__uuidof(MFFactory));
    if (FAILED(hr)) {
        wprintf(L"GLFactory.CoCreateInstance(__uuidof(MFFactory)); returned null - check SDK initialization and include paths\n");
        glDeleteTextures(1, &tex);
        CoUninitialize();
        return -1;
    }


    // Prepare M_AV_PROPS
    M_AV_PROPS avProps = {};
    avProps.vidProps.nWidth = texW;
    avProps.vidProps.nHeight = texH ;
    avProps.vidProps.fccType = eMFCC_Default;
    avProps.vidProps.eVideoFormat = eMVF_Custom;
    avProps.vidProps.dblRate = 30.0;

    // 5) create IMFFrame from GL texture and send in a loop
    CComQIPtr<IMFReceiver> cpReceiver(cpPreview);
    const double fps = 30.0;
    const int intervalMs = (int)(1000.0 / fps);

    while ((GetAsyncKeyState(VK_ESCAPE) & 0x8000) == 0) {
        CComPtr<IMFFrame> cpFrameFromGL;
        HRESULT hr = GLFactory->MFFrameCreateFromOpenGLTexture(&avProps, (LONGLONG)tex, 0, 0, &cpFrameFromGL, nullptr);
        if (SUCCEEDED(hr) && cpFrameFromGL) {
            if (cpReceiver) cpReceiver->ReceiverFramePut(cpFrameFromGL, -1, CComBSTR(L""));
           
        }
        else {
            wprintf(L"MFFrameCreateFromOpenGLTexture failed: 0x%08x\n", hr);
        }
        
    }

    wprintf(L"Stopping\n");
    glDeleteTextures(1, &tex);
    CoUninitialize();
    return 0;
}
