/**
 * Copyright 2013 JogAmp Community. 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 JogAmp Community ``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 JogAmp Community 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.
 * 
 * The views and conclusions contained in the software and documentation are those of the
 * authors and should not be interpreted as representing official policies, either expressed
 * or implied, of JogAmp Community.
 */
 
#include "ffmpeg_dshow.h"

#ifdef _WIN32

#include <stdio.h>
#include <string.h>

// dshow includes strsafe.h, hence tchar.h cannot be used
// include strsafe.h here for documentation
// #include <tchar.h>
#include <strsafe.h>
#include <dshow.h>

static HRESULT EnumerateDevices(REFGUID category, IEnumMoniker **ppEnum)
{
    // Create the System Device Enumerator.
    ICreateDevEnum *pDevEnum;
    void *pv = NULL;

    HRESULT hr = CoCreateInstance(&CLSID_SystemDeviceEnum, NULL, CLSCTX_INPROC_SERVER, &IID_ICreateDevEnum, (void**)&pDevEnum);

    if (SUCCEEDED(hr)) {
        // Create an enumerator for the category.
        hr = pDevEnum->lpVtbl->CreateClassEnumerator(pDevEnum, category, ppEnum,0);
        if (hr == S_FALSE)
        {
            hr = VFW_E_NOT_FOUND;  // The category is empty. Treat as an error.
        }
        pDevEnum->lpVtbl->Release(pDevEnum);
    }
    return hr;
}

static void getBSTRChars(BSTR bstr, char *pDest, int destLen) {

 #ifdef UNICODE
    // _sntprintf(pDest, destLen, _T("%s"), bstr);
    StringCbPrintfW(pDest, destLen, L"%s", bstr);
 #else
    // _sntprintf(pDest, destLen, _T("%S"), bstr);
    StringCchPrintfA(pDest, destLen, "%S", bstr);
 #endif 
}


static int GetDeviceInformation(IEnumMoniker *pEnum, int verbose, int devIdx,
                                char *pDescr, int descrSize,  
                                char *pName, int nameSize, 
                                char *pPath, int pathSize, int *pWaveID) {
    IMoniker *pMoniker = NULL;
    int i=0;
    int res = devIdx >= 0 ? -1 : 0;

    if( NULL != pDescr ) {
        *pDescr=0;
    }
    if( NULL != pName ) {
        *pName=0;
    }
    if( NULL != pPath ) {
        *pPath=0;
    }
    if( NULL != pWaveID ) {
        *pWaveID=0;
    }

    while (pEnum->lpVtbl->Next(pEnum, 1, &pMoniker, NULL) == S_OK) {
        IPropertyBag *pPropBag;
        HRESULT hr;
        
        hr = pMoniker->lpVtbl->BindToStorage(pMoniker, 0, 0, &IID_IPropertyBag, (void**)&pPropBag);
        if (FAILED(hr)) {
            if( verbose ) {
                fprintf(stderr, "DShowParser: Dev[%d]: bind failed ...\n", i);
            }
            pMoniker->lpVtbl->Release(pMoniker);
            continue;  
        } 
        VARIANT var;
        VariantInit(&var);

        // Get description or friendly name.
        hr = pPropBag->lpVtbl->Read(pPropBag, L"Description", &var, 0);
        if (SUCCEEDED(hr)) {
            if( i == devIdx && NULL != pDescr ) {
                res = 0;
                getBSTRChars(var.bstrVal, pDescr, descrSize);
            }
            if( verbose ) {
                fprintf(stderr, "DShowParser: Dev[%d]: Descr %S\n", i, var.bstrVal);
            }
            VariantClear(&var); 
        } else if( verbose ) {
            fprintf(stderr, "DShowParser: Dev[%d]: cannot read Descr..\n", i);
        }

        hr = pPropBag->lpVtbl->Read(pPropBag, L"FriendlyName", &var, 0);
        if (SUCCEEDED(hr)) {
            if( i == devIdx && NULL != pName ) {
                res = 0;
                getBSTRChars(var.bstrVal, pName, nameSize);
            }
            if( verbose ) {
                fprintf(stderr, "DShowParser: Dev[%d]: Name %S\n", i, var.bstrVal);
            }
            VariantClear(&var); 
        } else if( verbose ) {
            fprintf(stderr, "DShowParser: Dev[%d]: cannot read Name..\n", i);
        }

        hr = pPropBag->lpVtbl->Write(pPropBag, L"FriendlyName", &var);

        // WaveInID applies only to audio capture devices.
        hr = pPropBag->lpVtbl->Read(pPropBag, L"WaveInID", &var, 0);
        if (SUCCEEDED(hr)) {
            if( i == devIdx && NULL != pWaveID ) {
                res = 0;
                *pWaveID=(int)var.lVal;
            }
            if( verbose ) {
                fprintf(stderr, "DShowParser: Dev[%d]: WaveInID %d\n", i, var.lVal);
            }
            VariantClear(&var); 
        }

        hr = pPropBag->lpVtbl->Read(pPropBag, L"DevicePath", &var, 0);
        if (SUCCEEDED(hr)) {
            if( i == devIdx && NULL != pPath ) {
                res = 0;
                getBSTRChars(var.bstrVal, pPath, pathSize);
            }
            if( verbose ) {
                fprintf(stderr, "DShowParser: Dev[%d]: Path %S\n", i, var.bstrVal);
            }
            VariantClear(&var); 
        }

        pPropBag->lpVtbl->Release(pPropBag);
        pMoniker->lpVtbl->Release(pMoniker);

        if( devIdx >= 0 && i == devIdx ) {
            break; // done!
        }
        i++;
    }
    return res;
}

int findDShowVideoDevice(char * dest, int destSize, int devIdx, int verbose) {
    int res = -1;

    HRESULT hr = CoInitializeEx(NULL, COINIT_MULTITHREADED);
    if (SUCCEEDED(hr)) {
        IEnumMoniker *pEnum;

        hr = EnumerateDevices(&CLSID_VideoInputDeviceCategory, &pEnum);
        if (SUCCEEDED(hr)) {
            res = GetDeviceInformation(pEnum, verbose, devIdx, NULL /* pDescr */, 0, dest, destSize, NULL /* pPath */, 0, NULL /* pWaveID */);
            pEnum->lpVtbl->Release(pEnum);
            if( verbose ) {
                fprintf(stderr, "DShowParser: Get VideoInputDevice: res %d, '%s'\n", res, dest);
            }
        } else if( verbose ) {
            fprintf(stderr, "DShowParser: Get VideoInputDevice failed\n");
        }
        /**
        hr = EnumerateDevices(&CLSID_AudioInputDeviceCategory, &pEnum);
        if (SUCCEEDED(hr)) {
            res = GetDeviceInformation(pEnum, verbose, devIdx, NULL, 0, NULL, 0, NULL, 0, NULL);
            pEnum->lpVtbl->Release(pEnum);
        } else if( verbose ) {
            fprintf(stderr, "DShowParser: Get AudioInputDevice failed\n");
        } */
        CoUninitialize();
    } else if( verbose ) {
        fprintf(stderr, "DShowParser: CoInit failed\n");
    }
    return res;
}

#else

int findDShowVideoDevice(char * dest, int destSize, int devIdx, int verbose) {
    return -1;
}

#endif