#ifdef WIN32

#include "vn_capture_dshow.h"
extern "C" {
#include "libavdevice/avdevice.h"
#include "libswscale/swscale.h"
#include "libavformat/avformat.h"
#include "libavcodec/avcodec.h"
#include "libavdevice/dshow_capture.h"
}

static char *dup_wchar_to_utf8(wchar_t *w)
{
    char *s = NULL;
    int l = WideCharToMultiByte(CP_UTF8, 0, w, -1, 0, 0, 0, 0);
    s = (char*)calloc(1, l);
    if (s)
        WideCharToMultiByte(CP_UTF8, 0, w, -1, s, l, 0, 0);
    return s;
}

char **vn_capture_get_device_list(int devtype)
{
    IGraphBuilder *graph = NULL;
    ICreateDevEnum *devenum = NULL;
    int r;
    IEnumMoniker *classenum = NULL;
    IMoniker *m = NULL;
    int i = 0;

    const GUID *device_guid[2] = { &CLSID_VideoInputDeviceCategory,
                                   &CLSID_AudioInputDeviceCategory };
    const char *devtypename = (devtype == VideoDevice) ? "video" : "audio";

    char **devices = (char**)calloc(1, sizeof(char*)*128);

    CoInitialize(0);

    r = CoCreateInstance(CLSID_FilterGraph, NULL, CLSCTX_INPROC_SERVER,
                         IID_IGraphBuilder, (void **) &graph);
    if (r != S_OK) {
        fprintf(stderr, "Could not create capture graph.\n");
        goto fail;
    }

    r = CoCreateInstance(CLSID_SystemDeviceEnum, NULL, CLSCTX_INPROC_SERVER,
                         IID_ICreateDevEnum, (void **) &devenum);
    if (r != S_OK) {
        fprintf(stderr, "Could not enumerate system devices.\n");
        goto fail;
    }


    r = ICreateDevEnum_CreateClassEnumerator(devenum, *device_guid[devtype],
                                             (IEnumMoniker **) &classenum, 0);
    if (r != S_OK) {
        fprintf(stderr, "Could not enumerate %s devices.\n",
               devtypename);
        goto fail;
    }

    while (IEnumMoniker_Next(classenum, 1, &m, NULL) == S_OK) {
        IPropertyBag *bag = NULL;
        char *buf = NULL;
        VARIANT var;

       
        r = IMoniker_BindToStorage(m, 0, 0, IID_IPropertyBag, (void *) &bag);
        if (r != S_OK)
            goto fail1;

        var.vt = VT_BSTR;
        r = IPropertyBag_Read(bag, L"FriendlyName", &var, NULL);
        if (r != S_OK)
            goto fail1;
        buf = dup_wchar_to_utf8(var.bstrVal);

        devices[i++] = buf;
//        fprintf(stderr, " \"%s\"\n", buf);
        
fail1:
        if (bag)
            IPropertyBag_Release(bag);
        IMoniker_Release(m);


        if (i > 126)
            break;
    }
    
fail:
    if (classenum)
        IEnumMoniker_Release(classenum);
    if (devenum)
        ICreateDevEnum_Release(devenum);

    return devices;
}

void vn_capture_free_device_list(char **devices)
{
    char **dev = devices;
    for (char **d = dev; *d; d++)
    {
        free(*d);
    }

    free(devices);
}

static int
dshow_filter_device(const char *device_name, ICreateDevEnum *devenum,
                    int devtype, IBaseFilter **pfilter)
{
    IBaseFilter *device_filter = NULL;
    IEnumMoniker *classenum = NULL;
    IMoniker *m = NULL;
    int r;

    const GUID *device_guid[2] = { &CLSID_VideoInputDeviceCategory,
                                   &CLSID_AudioInputDeviceCategory };
    const char *devtypename = (devtype == VideoDevice) ? "video" : "audio";

    r = ICreateDevEnum_CreateClassEnumerator(devenum, *device_guid[devtype],
                                             (IEnumMoniker **) &classenum, 0);
    if (r != S_OK) {
        fprintf(stderr, "Could not enumerate %s devices.\n",
                devtypename);
        return -1;
    }

    while (!device_filter && IEnumMoniker_Next(classenum, 1, &m, NULL) == S_OK) {
        IPropertyBag *bag = NULL;
        char *buf = NULL;
        VARIANT var;

        r = IMoniker_BindToStorage(m, 0, 0, IID_IPropertyBag, (void *) &bag);
        if (r != S_OK)
            goto fail2;

        var.vt = VT_BSTR;
        r = IPropertyBag_Read(bag, L"FriendlyName", &var, NULL);
        if (r != S_OK)
            goto fail2;

        buf = dup_wchar_to_utf8(var.bstrVal);
        if (pfilter) {
            if (strcmp(device_name, buf)) {
                goto fail2;
            }

            IMoniker_BindToObject(m, 0, 0, IID_IBaseFilter, (void *) &device_filter);
        }

fail2:
        if (buf)
           free(buf);
        if (bag)
            IPropertyBag_Release(bag);
        IMoniker_Release(m);
    }

    IEnumMoniker_Release(classenum);

    if (pfilter) {
        if (!device_filter) {
            fprintf(stderr, "Could not find %s device.\n",
                    devtypename);
            return -1;
        }
        *pfilter = device_filter;
    }

    return 0;
}

char **vn_capture_get_resolutions_list(const char *device_name)
{
    IGraphBuilder *graph = NULL;
    ICreateDevEnum *devenum = NULL;
    int r;
    IBaseFilter *device_filter = NULL;
    IEnumMoniker *classenum = NULL;
    //IMoniker *m = NULL;
    //int i = 0;
    int rs = 0;
    IEnumPins *pins = 0;
    IPin *pin;
    
    const GUID *device_guid[2] = { &CLSID_VideoInputDeviceCategory,
                                   &CLSID_AudioInputDeviceCategory };

    char **resolutions = (char**)calloc(1, sizeof(char*)*128);

    CoInitialize(0);

    r = CoCreateInstance(CLSID_FilterGraph, NULL, CLSCTX_INPROC_SERVER,
                         IID_IGraphBuilder, (void **) &graph);
    if (r != S_OK) {
        fprintf(stderr, "Could not create capture graph.\n");
        goto fail3;
    }

    r = CoCreateInstance(CLSID_SystemDeviceEnum, NULL, CLSCTX_INPROC_SERVER,
                         IID_ICreateDevEnum, (void **) &devenum);
    if (r != S_OK) {
        fprintf(stderr, "Could not enumerate system devices.\n");
        goto fail3;
    }


    r = ICreateDevEnum_CreateClassEnumerator(devenum, *device_guid[0],
                                             (IEnumMoniker **) &classenum, 0);
    if (r != S_OK) {
        fprintf(stderr, "Could not enumerate devices.\n");
        goto fail3;
    }

    if ((r = dshow_filter_device(device_name, devenum, VideoDevice, &device_filter)) < 0)
        return 0;

    r = IBaseFilter_EnumPins(device_filter, &pins);
    if (r != S_OK) {
        fprintf(stderr, "Could not enumerate pins.\n");
        return 0;
    }
    while (IEnumPins_Next(pins, 1, &pin, NULL) == S_OK)
    {
        IKsPropertySet *p = NULL;
        IEnumMediaTypes *types = NULL;
        PIN_INFO info = {0};
        GUID category;
        DWORD r2;

        IPin_QueryPinInfo(pin, &info);
        IBaseFilter_Release(info.pFilter);

        if (info.dir != PINDIR_OUTPUT)
            goto next;
        if (IPin_QueryInterface(pin, IID_IKsPropertySet, (void **) &p) != S_OK)
            goto next;
        if (IKsPropertySet_Get(p, AMPROPSETID_Pin, AMPROPERTY_PIN_CATEGORY,
                               NULL, 0, &category, sizeof(GUID), &r2) != S_OK)
            goto next;
        if (!IsEqualGUID(category, PIN_CATEGORY_CAPTURE))
            goto next;

        if (1/*!ppin*/) {
            char *buf = dup_wchar_to_utf8(info.achName);
//            fprintf(stderr, " Pin \"%s\"\n", buf);
            free(buf);
            IAMStreamConfig *config = NULL;
            AM_MEDIA_TYPE *type = NULL;
            void *caps = NULL;
            int i, n, size;

            if (IPin_QueryInterface(pin, IID_IAMStreamConfig, (void **) &config) != S_OK)
                continue;
            if (IAMStreamConfig_GetNumberOfCapabilities(config, &n, &size) != S_OK)
                continue;
            caps = av_malloc(size);

            for (i = 0; i < n; i++)
            {
                IAMStreamConfig_GetStreamCaps(config, i, &type, (void *) caps);

                VIDEO_STREAM_CONFIG_CAPS *vcaps = (VIDEO_STREAM_CONFIG_CAPS*)caps;
                BITMAPINFOHEADER *bih;
                //int64_t *fr = 0;
                if (IsEqualGUID(type->formattype, FORMAT_VideoInfo)) {
                    VIDEOINFOHEADER *v = (VIDEOINFOHEADER *) type->pbFormat;
                    //fr = &v->AvgTimePerFrame;
                    bih = &v->bmiHeader;
                } else if (IsEqualGUID(type->formattype, FORMAT_VideoInfo2)) {
                    VIDEOINFOHEADER2 *v = (VIDEOINFOHEADER2 *) type->pbFormat;
                    //fr = &v->AvgTimePerFrame;
                    bih = &v->bmiHeader;
                } else {
                    goto next2;
                }
                
                fprintf(stderr, "Compress: 0x%08lx\n",  bih->biCompression);
                fprintf(stderr, "   ----%ldx%ld\n", vcaps->MaxOutputSize.cx, vcaps->MaxOutputSize.cy);

                
                if (bih->biCompression == 0x30323449) // I420
                {
                    char *res = (char*)malloc(128);
                    snprintf(res, 127, "%ldx%ld/I420/%g", vcaps->MaxOutputSize.cx, vcaps->MaxOutputSize.cy, 1e7 / vcaps->MinFrameInterval);

                    if (rs > 0 && strcmp(resolutions[rs-1], res) == 0)
                    {
                        free(res);
                    }
                    else
                    {
                        resolutions[rs++] = res;
                    }

                }
                else if (bih->biCompression == 0x32595559) // YUYV422
                {
                    char *res = (char*)malloc(128);
                    snprintf(res, 127, "%ldx%ld/YUY2/%g", vcaps->MaxOutputSize.cx, vcaps->MaxOutputSize.cy, 1e7 / vcaps->MinFrameInterval);

                    if (rs > 0 && strcmp(resolutions[rs-1], res) == 0)
                    {
                        free(res);
                    }
                    else
                    {
                        resolutions[rs++] = res;
                    }
                }

next2:
                if (type->pbFormat)
                    CoTaskMemFree(type->pbFormat);
                CoTaskMemFree(type);
            }
            IAMStreamConfig_Release(config);
            if (caps)
                av_free(caps);
            goto next;
        }
    
next:
        if (types)
            IEnumMediaTypes_Release(types);
        if (p)
            IKsPropertySet_Release(p);
    }

    
    
fail3:
    if (classenum)
        IEnumMoniker_Release(classenum);
    if (devenum)
        ICreateDevEnum_Release(devenum);

    return resolutions;
}

void vn_capture_free_resolutions_list(char **resolutions)
{
    char **res = resolutions;
    for (char **r = res; *r; r++)
    {
        free(*r);
    }

    free(resolutions);
}


#endif
