#include "directshow_control.h"

#ifdef _WIN32

#include <iostream>
#include <sstream>

DirectShowControl::DirectShowControl() : m_initialized(false) {
    CoInitialize(nullptr);
}

DirectShowControl::~DirectShowControl() {
    releaseDevice();
    CoUninitialize();
}

HRESULT DirectShowControl::InitializeDirectShow() {
    HRESULT hr = S_OK;
    
    // Create the Filter Graph Manager
    hr = m_pGraph.CoCreateInstance(CLSID_FilterGraph);
    if (FAILED(hr)) return hr;
    
    // Create the Capture Graph Builder
    hr = m_pCapture.CoCreateInstance(CLSID_CaptureGraphBuilder2);
    if (FAILED(hr)) return hr;
    
    // Set the filter graph
    hr = m_pCapture->SetFiltergraph(m_pGraph);
    if (FAILED(hr)) return hr;
    
    return hr;
}

HRESULT DirectShowControl::FindLogitechMXBrio() {
    HRESULT hr = S_OK;
    CComPtr<ICreateDevEnum> pDevEnum;
    CComPtr<IEnumMoniker> pClassEnum;
    
    // Create the System Device Enumerator
    hr = pDevEnum.CoCreateInstance(CLSID_SystemDeviceEnum);
    if (FAILED(hr)) return hr;
    
    // Create an enumerator for video capture devices
    hr = pDevEnum->CreateClassEnumerator(CLSID_VideoInputDeviceCategory, &pClassEnum, 0);
    if (FAILED(hr)) return hr;
    
    CComPtr<IMoniker> pMoniker;
    while (pClassEnum->Next(1, &pMoniker, nullptr) == S_OK) {
        CComPtr<IPropertyBag> pPropBag;
        hr = pMoniker->BindToStorage(0, 0, IID_PPV_ARGS(&pPropBag));
        if (SUCCEEDED(hr)) {
            VARIANT var;
            VariantInit(&var);
            
            // Get the friendly name
            hr = pPropBag->Read(L"FriendlyName", &var, 0);
            if (SUCCEEDED(hr)) {
                std::wstring friendlyName = var.bstrVal;
                
                // Check if this is a Logitech MX Brio or BRIO
                if (friendlyName.find(L"MX Brio") != std::wstring::npos ||
                    friendlyName.find(L"BRIO") != std::wstring::npos ||
                    friendlyName.find(L"Logitech") != std::wstring::npos) {
                    
                    // Bind to the filter
                    hr = pMoniker->BindToObject(nullptr, nullptr, IID_IBaseFilter, 
                                              reinterpret_cast<void**>(&m_pVideoCaptureFilter));
                    if (SUCCEEDED(hr)) {
                        // Get camera control interface
                        hr = m_pVideoCaptureFilter->QueryInterface(IID_IAMCameraControl,
                                                                 reinterpret_cast<void**>(&m_pCameraControl));
                        if (SUCCEEDED(hr)) {
                            // Get video proc amp interface
                            m_pVideoCaptureFilter->QueryInterface(IID_IAMVideoProcAmp,
                                                                reinterpret_cast<void**>(&m_pVideoProcAmp));
                            VariantClear(&var);
                            return S_OK;
                        }
                    }
                }
            }
            VariantClear(&var);
        }
        pMoniker.Release();
    }
    
    return E_FAIL;
}

void DirectShowControl::Cleanup() {
    m_pCameraControl.Release();
    m_pVideoProcAmp.Release();
    m_pVideoCaptureFilter.Release();
    m_pCapture.Release();
    m_pGraph.Release();
    m_initialized = false;
}

std::vector<CameraDevice> DirectShowControl::discoverDevices() {
    std::vector<CameraDevice> devices;
    HRESULT hr = S_OK;
    CComPtr<ICreateDevEnum> pDevEnum;
    CComPtr<IEnumMoniker> pClassEnum;
    
    // Create the System Device Enumerator
    hr = pDevEnum.CoCreateInstance(CLSID_SystemDeviceEnum);
    if (FAILED(hr)) return devices;
    
    // Create an enumerator for video capture devices
    hr = pDevEnum->CreateClassEnumerator(CLSID_VideoInputDeviceCategory, &pClassEnum, 0);
    if (FAILED(hr)) return devices;
    
    CComPtr<IMoniker> pMoniker;
    int deviceIndex = 0;
    
    while (pClassEnum->Next(1, &pMoniker, nullptr) == S_OK) {
        CComPtr<IPropertyBag> pPropBag;
        hr = pMoniker->BindToStorage(0, 0, IID_PPV_ARGS(&pPropBag));
        if (SUCCEEDED(hr)) {
            VARIANT var;
            VariantInit(&var);
            
            // Get the friendly name
            hr = pPropBag->Read(L"FriendlyName", &var, 0);
            if (SUCCEEDED(hr)) {
                std::wstring wName = var.bstrVal;
                
                // Check if this is a Logitech device
                if (wName.find(L"Logitech") != std::wstring::npos || 
                    wName.find(L"MX Brio") != std::wstring::npos ||
                    wName.find(L"BRIO") != std::wstring::npos) {
                    CameraDevice device;
                    
                    // Convert wide string to string
                    int len = WideCharToMultiByte(CP_UTF8, 0, wName.c_str(), -1, nullptr, 0, nullptr, nullptr);
                    std::string name(len, 0);
                    WideCharToMultiByte(CP_UTF8, 0, wName.c_str(), -1, &name[0], len, nullptr, nullptr);
                    name.pop_back(); // Remove null terminator
                    
                    device.name = name;
                    device.id = std::to_string(deviceIndex);
                    device.vendorId = 0x046d; // Logitech vendor ID
                    
                    // Try to determine product ID
                    if (wName.find(L"MX Brio") != std::wstring::npos) {
                        device.productId = 0x085e; // MX Brio product ID
                    } else if (wName.find(L"BRIO") != std::wstring::npos) {
                        device.productId = 0x085b; // BRIO 4K product ID
                    }
                    
                    devices.push_back(device);
                }
            }
            VariantClear(&var);
        }
        pMoniker.Release();
        deviceIndex++;
    }
    
    return devices;
}

bool DirectShowControl::initializeDevice(const std::string& deviceId) {
    if (m_initialized) {
        releaseDevice();
    }
    
    HRESULT hr = InitializeDirectShow();
    if (FAILED(hr)) return false;
    
    hr = FindLogitechMXBrio();
    if (FAILED(hr)) {
        Cleanup();
        return false;
    }
    
    m_initialized = true;
    return true;
}

void DirectShowControl::releaseDevice() {
    if (m_initialized) {
        Cleanup();
    }
}

bool DirectShowControl::getZoomCapabilities(ZoomCapabilities& caps) {
    if (!m_initialized || !m_pCameraControl) return false;
    
    long min, max, step, defaultValue, flags;
    HRESULT hr = m_pCameraControl->GetRange(CameraControl_Zoom, &min, &max, &step, &defaultValue, &flags);
    
    if (SUCCEEDED(hr)) {
        caps.min = static_cast<int>(min);
        caps.max = static_cast<int>(max);
        caps.step = static_cast<int>(step);
        caps.supportsAbsolute = true;
        caps.supportsRelative = (flags & CameraControl_Flags_Manual) != 0;
        
        // Get current zoom value
        long current;
        hr = m_pCameraControl->Get(CameraControl_Zoom, &current, &flags);
        if (SUCCEEDED(hr)) {
            caps.current = static_cast<int>(current);
        }
        
        return true;
    }
    
    return false;
}

bool DirectShowControl::setZoomAbsolute(int value) {
    if (!m_initialized || !m_pCameraControl) return false;
    
    HRESULT hr = m_pCameraControl->Set(CameraControl_Zoom, value, CameraControl_Flags_Manual);
    return SUCCEEDED(hr);
}

bool DirectShowControl::setZoomRelative(int value) {
    if (!m_initialized || !m_pCameraControl) return false;
    
    // For relative zoom, we need to get current value and adjust
    long current, flags;
    HRESULT hr = m_pCameraControl->Get(CameraControl_Zoom, &current, &flags);
    if (FAILED(hr)) return false;
    
    long newValue = current + value;
    
    // Get zoom range to clamp the value
    long min, max, step, defaultValue;
    hr = m_pCameraControl->GetRange(CameraControl_Zoom, &min, &max, &step, &defaultValue, &flags);
    if (FAILED(hr)) return false;
    
    // Clamp the value
    if (newValue < min) newValue = min;
    if (newValue > max) newValue = max;
    
    hr = m_pCameraControl->Set(CameraControl_Zoom, newValue, CameraControl_Flags_Manual);
    return SUCCEEDED(hr);
}

bool DirectShowControl::getZoomValue(int& value) {
    if (!m_initialized || !m_pCameraControl) return false;
    
    long current, flags;
    HRESULT hr = m_pCameraControl->Get(CameraControl_Zoom, &current, &flags);
    if (SUCCEEDED(hr)) {
        value = static_cast<int>(current);
        return true;
    }
    
    return false;
}

#endif // _WIN32