/** \file
 * \brief Video Capture Using Direct Show 9
 *
 * See Copyright Notice in im.h
 * $Id: Exp $
 */

/*
  The Direct Show Graph is composed by 3 components
  capture source, grabber transform and null renderer.

  Filters are connected:
    capture_filter(out)->(int)grabber_filter(out)->(in)null_filter

  We do not use MFC, ATL and the Direct Show Base Classes.

  We had 3 possible solutions:
    - build a custom filter and use the defaul video renderer
      but this is a heavy work without the Base Classes.
    - use the ISampleGrabberCB to implement the sample callback,
      but the data can not be changed, so it is useless to use the defaul video renderer
      and we have to buffer the data any way.
    - so we use the buffer of the ISampleGrabber. but this can not be done in a callback
      and we leave the grab loop for the application, it can also be done in the idle function.

  If you use the idle function the Source Dialog is not "live" because it is modal and 
  it does not use the application message loop.
  So the application never has an opportunity to grabb a frame.
  It could be live if we use the default video renderer. But if you use a timer will work.

  Since there is no gray format, bpp is always 24bpp.
*/

#include <dshow.h>
#include <qedit.h>

#include <memory.h>
#include <stdio.h>
#include <assert.h>

#include "imvideocapture.h"

#define VC_CAMERADELAY 200 // This vary from camera to camera, so we use a reasonable value.

/**************************************************************************
                       Capture Devices
***************************************************************************/

#define IMVC_MAXVIDDEVICES  30

static int vc_DeviceCount = 0;
static char vc_DeviceDesc[IMVC_MAXVIDDEVICES][160];

static char* Wide2Char(WCHAR* wstr)
{
  if (wstr)
  {
    int n = wcslen(wstr)+1;
    char* str = (char*)malloc(n);
    WideCharToMultiByte(CP_ACP, 0, wstr, -1, str, n, NULL, NULL);
    return str;
  }

  return NULL;
}

static void vc_EnumerateDevices(void)
{
  // Selecting a Capture Device
  ICreateDevEnum *pDevEnum = NULL;
  IEnumMoniker *pEnum = NULL;

  CoInitialize(NULL);

  // Create the System Device Enumerator.
  HRESULT hr = CoCreateInstance(CLSID_SystemDeviceEnum, NULL,
                                CLSCTX_INPROC_SERVER, IID_ICreateDevEnum, 
                                reinterpret_cast<void**>(&pDevEnum));
  if (!SUCCEEDED(hr))
    return;

  // Create an enumerator for the video capture category.
  hr = pDevEnum->CreateClassEnumerator(CLSID_VideoInputDeviceCategory, &pEnum, 0);
  if (!SUCCEEDED(hr) || !pEnum)
  {
    pDevEnum->Release();
    return;
  }

  IMoniker *pMoniker = NULL;
  while (pEnum->Next(1, &pMoniker, NULL) == S_OK)
  {
    IPropertyBag *pPropBag;
    hr = pMoniker->BindToStorage(0, 0, IID_IPropertyBag, (void**)(&pPropBag));
    if (FAILED(hr))
    {
      pMoniker->Release();
      continue;  // Skip this one, maybe the next one will work.
    } 

    // Find the friendly name.
    VARIANT varName;
    VariantInit(&varName);

    hr = pPropBag->Read(L"FriendlyName", &varName, 0);
    if (SUCCEEDED(hr))
    {
      char* str = Wide2Char(varName.bstrVal);
      sprintf(vc_DeviceDesc[vc_DeviceCount], "%d - %s", vc_DeviceCount, str);
      free(str);
      VariantClear(&varName); 
      vc_DeviceCount++;
    }

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

  pEnum->Release();
  pDevEnum->Release();
}

static IBaseFilter* vc_GetDevice(int device)
{
  // Selecting a Capture Device
  ICreateDevEnum *pDevEnum = NULL;
  IEnumMoniker *pEnum = NULL;

  // Create the System Device Enumerator.
  HRESULT hr = CoCreateInstance(CLSID_SystemDeviceEnum, NULL,
                                CLSCTX_INPROC_SERVER, IID_ICreateDevEnum, 
                                reinterpret_cast<void**>(&pDevEnum));
  if (!SUCCEEDED(hr))
    return NULL;

  // Create an enumerator for the video capture category.
  hr = pDevEnum->CreateClassEnumerator(CLSID_VideoInputDeviceCategory, &pEnum, 0);
  if (!SUCCEEDED(hr) || !pEnum)
  {
    pDevEnum->Release();
    return NULL;
  }

  IMoniker *pMoniker = NULL;
  int d = 0;
  while (pEnum->Next(1, &pMoniker, NULL) == S_OK)
  {
    if (d == device)
    {
      //create the capture filter for the device 
      //add the filter to the filter graph
      IBaseFilter *capture_filter = NULL;
      hr = pMoniker->BindToObject(0, 0, IID_IBaseFilter, (void**)&capture_filter);
      if (SUCCEEDED(hr))
      {
        pMoniker->Release();
        pEnum->Release();
        pDevEnum->Release();
        return capture_filter;
      }
      else
      {
        pMoniker->Release();
        break;
      }
    }

    pMoniker->Release();
    d++;
  }

  pEnum->Release();
  pDevEnum->Release();
  return NULL;
}

static IPin* vc_GetPin(IBaseFilter* pFilter, PIN_DIRECTION dir)
{
  IEnumPins*  pEnumPins = NULL;
  IPin*       pPin = NULL;

  pFilter->EnumPins(&pEnumPins);
  if(!pEnumPins)
    return NULL;

  for(;;)
  {
    ULONG  cFetched = 0;
    PIN_DIRECTION pinDir = PIN_DIRECTION(-1); 
    pPin = 0;

    if (FAILED(pEnumPins->Next(1, &pPin, &cFetched)))
    {
      pEnumPins->Release();
      return NULL;
    }

    if(cFetched == 1 && pPin != 0)
    {
      pPin->QueryDirection(&pinDir);
      if(pinDir == dir) break;
      pPin->Release();
    }
  }

  pEnumPins->Release();
  return pPin;
}

static int vc_DisconnectFilters(IGraphBuilder* filter_builder, IBaseFilter* source, IBaseFilter* destiny)
{
  IPin *pOut = vc_GetPin(source, PINDIR_OUTPUT);
  IPin *pIn = vc_GetPin(destiny, PINDIR_INPUT);
  HRESULT hr = filter_builder->Disconnect(pOut);
  hr = filter_builder->Disconnect(pIn);
  pOut->Release(); 
  pIn->Release();
  if (FAILED(hr))  return 0;
  return 1;
}

static int vc_ConnectFilters(IGraphBuilder* filter_builder, IBaseFilter* source, IBaseFilter* destiny)
{
  IPin *pOut = vc_GetPin(source, PINDIR_OUTPUT);
  IPin *pIn = vc_GetPin(destiny, PINDIR_INPUT);
  HRESULT hr = filter_builder->Connect(pOut, pIn);
  pOut->Release(); 
  pIn->Release();
  if (FAILED(hr))  return 0;
  return 1;
}

static int vc_ShowPropertyPages(HWND parent, IUnknown* obj, WCHAR* title)
{
  ISpecifyPropertyPages *pSpec;

  HRESULT hr = obj->QueryInterface(IID_ISpecifyPropertyPages,  (void **)&pSpec);
  if (FAILED(hr)) return 0;

  CAUUID cauuid;
  hr = pSpec->GetPages(&cauuid);
  if (FAILED(hr)) return -1;

  hr = OleCreatePropertyFrame(parent, 30, 30, title, 1,
                        &obj, cauuid.cElems, (GUID *)cauuid.pElems, 0, 0, NULL);
  if (FAILED(hr)) return 0;

  CoTaskMemFree(cauuid.pElems);
  pSpec->Release();

  return 1;
}

static double vc_Value2Percent(long Min, long Max, long Val)
{
  return ((Val - Min)*100.)/((double)(Max - Min));
}

static long vc_Percent2Value(long Min, long Max, long Step, double Per)
{
  long Val = (long)((Per/100.)*(Max - Min) + Min);
  if (Step == 1)
    return Val;

  long num_step = (Val - Min + Step-1) / Step;
  return num_step*Step + Min;
}

static IAMVideoProcAmp* vc_InitVideoProcAmp(IBaseFilter* capture_filter, IAMVideoProcAmp* *video_prop)
{
  if (*video_prop)
    return *video_prop;

  HRESULT hr = capture_filter->QueryInterface(IID_IAMVideoProcAmp, (void**)video_prop);
  if (FAILED(hr))
    return NULL;

  return *video_prop;
}

static int vc_SetVideoProcAmpProperty(IBaseFilter* capture_filter, IAMVideoProcAmp* *video_prop, long property, double percent)
{
  IAMVideoProcAmp *pProp = vc_InitVideoProcAmp(capture_filter, video_prop);
  if (!pProp) return 0;
  HRESULT hr;
  VideoProcAmpProperty prop = (VideoProcAmpProperty)property;
  long Min, Max, Step, Default, Flags;
  hr = pProp->GetRange(prop, &Min, &Max, &Step, &Default, &Flags);
  hr = pProp->Set(prop, vc_Percent2Value(Min, Max, Step, percent), VideoProcAmp_Flags_Manual);
  if (FAILED(hr)) return 0;
  return 1;
}

static int vc_GetVideoProcAmpProperty(IBaseFilter* capture_filter, IAMVideoProcAmp* *video_prop, long property, double *percent)
{
  IAMVideoProcAmp *pProp = vc_InitVideoProcAmp(capture_filter, video_prop);
  if (!pProp) return 0;

  HRESULT hr;
  VideoProcAmpProperty prop = (VideoProcAmpProperty)property;
  long Min, Max, Step, Default, Flags, Val;
  hr = pProp->GetRange(prop, &Min, &Max, &Step, &Default, &Flags);
  hr = pProp->Get(prop, &Val, &Flags);

  if (FAILED(hr)) return 0;
  *percent = vc_Value2Percent(Min, Max, Val);
  return 1;
}

static int vc_ResetVideoProcAmpProperty(IBaseFilter* capture_filter, IAMVideoProcAmp* *video_prop, long property, int fauto)
{
  IAMVideoProcAmp *pProp = vc_InitVideoProcAmp(capture_filter, video_prop);
  if (!pProp) return 0;

  HRESULT hr;
  VideoProcAmpProperty prop = (VideoProcAmpProperty)property;
  long Min, Max, Step, Default, Flags;
  hr = pProp->GetRange(prop, &Min, &Max, &Step, &Default, &Flags);

  if (fauto && (Flags & VideoProcAmp_Flags_Auto))
    hr = pProp->Set(prop, Default, VideoProcAmp_Flags_Auto);
  else
    hr = pProp->Set(prop, Default, VideoProcAmp_Flags_Manual);

  if (FAILED(hr)) return 0;
  return 1;
}

static IAMCameraControl* vc_InitCameraControl(IBaseFilter* capture_filter, IAMCameraControl* *camera_prop)
{
  if (*camera_prop)
    return *camera_prop;

  HRESULT hr = capture_filter->QueryInterface(IID_IAMCameraControl, (void**)camera_prop);
  if (FAILED(hr))
    return NULL;

  return *camera_prop;
}

static int vc_SetCameraControlProperty(IBaseFilter* capture_filter, IAMCameraControl* *camera_prop, long property, double percent)
{
  IAMCameraControl *pProp = vc_InitCameraControl(capture_filter, camera_prop);
  if (!pProp) return 0;

  HRESULT hr;
  CameraControlProperty prop = (CameraControlProperty)property;
  long Min, Max, Step, Default, Flags;
  hr = pProp->GetRange(prop, &Min, &Max, &Step, &Default, &Flags);
  hr = pProp->Set(prop, vc_Percent2Value(Min, Max, Step, percent), CameraControl_Flags_Manual);

  if (FAILED(hr)) return 0;
  return 1;
}

static int vc_GetCameraControlProperty(IBaseFilter* capture_filter, IAMCameraControl* *camera_prop, long property, double *percent)
{
  IAMCameraControl *pProp = vc_InitCameraControl(capture_filter, camera_prop);
  if (!pProp) return 0;

  HRESULT hr;
  CameraControlProperty prop = (CameraControlProperty)property;
  long Min, Max, Step, Default, Flags, Val;
  hr = pProp->GetRange(prop, &Min, &Max, &Step, &Default, &Flags);
  hr = pProp->Get(prop, &Val, &Flags);

  if (FAILED(hr)) return 0;
  *percent = vc_Value2Percent(Min, Max, Val);
  return 1;
}

static int vc_ResetCameraControlProperty(IBaseFilter* capture_filter, IAMCameraControl* *camera_prop, long property, int fauto)
{
  IAMCameraControl *pProp = vc_InitCameraControl(capture_filter, camera_prop);
  if (!pProp) return 0;

  HRESULT hr;
  CameraControlProperty prop = (CameraControlProperty)property;
  long Min, Max, Step, Default, Flags;
  hr = pProp->GetRange(prop, &Min, &Max, &Step, &Default, &Flags);

  if (fauto && (Flags & CameraControl_Flags_Auto))
    hr = pProp->Set(prop, Default, CameraControl_Flags_Auto);
  else
    hr = pProp->Set(prop, Default, CameraControl_Flags_Manual);

  if (FAILED(hr)) return 0;
  return 1;
}

static IAMVideoControl* vc_InitVideoControl(IBaseFilter* capture_filter, IAMVideoControl* *video_prop)
{
  if (*video_prop)
    return *video_prop;

  HRESULT hr = capture_filter->QueryInterface(IID_IAMVideoControl, (void**)video_prop);
  if (FAILED(hr))
    return NULL;

  return *video_prop;
}

static int vc_SetVideoControlProperty(IBaseFilter* capture_filter, IAMVideoControl* *video_prop, long property, double percent)
{
  IAMVideoControl *pProp = vc_InitVideoControl(capture_filter, video_prop);
  if (!pProp) return 0;

  HRESULT hr;
  IPin *pOutPin = vc_GetPin(capture_filter, PINDIR_OUTPUT);
  long Mode;
  hr = pProp->GetMode(pOutPin, &Mode);
  if (percent)
    Mode = Mode | property;
  else
    Mode = Mode & ~property;
  hr = pProp->SetMode(pOutPin, Mode);
  pOutPin->Release();

  if (FAILED(hr)) return 0;
  return 1;
}

static int vc_GetVideoControlProperty(IBaseFilter* capture_filter, IAMVideoControl* *video_prop, long property, double *percent)
{
  IAMVideoControl *pProp = vc_InitVideoControl(capture_filter, video_prop);
  if (!pProp) return 0;

  HRESULT hr;
  long Mode;
  IPin *pOutPin = vc_GetPin(capture_filter, PINDIR_OUTPUT);
  hr = pProp->GetMode(pOutPin, &Mode);
  pOutPin->Release();

  if (FAILED(hr)) return 0;
  if (Mode & property)
    *percent = 100.;
  else
    *percent = 0.;
  return 1;
}

static int vc_ResetVideoControlProperty(IBaseFilter* capture_filter, IAMVideoControl* *video_prop, long property, int fauto)
{
  IAMVideoControl *pProp = vc_InitVideoControl(capture_filter, video_prop);
  if (!pProp) return 0;

  HRESULT hr;
  long Mode;
  IPin *pOutPin = vc_GetPin(capture_filter, PINDIR_OUTPUT);
  hr = pProp->GetMode(pOutPin, &Mode);
  if (Mode & property)
    Mode = Mode & ~property;
  else
    Mode = Mode | property;
  hr = pProp->SetMode(pOutPin, Mode);
  pOutPin->Release();

  if (FAILED(hr)) return 0;
  return 1;
}

/**************************************************************************
                          imVideoCapture
***************************************************************************/

struct _imVideoCapture
{
  int live,
      device;                     /* current connected device. -1 if not connected. */

  IGraphBuilder* filter_builder;  /* The Filter Graph Manager */
  ICaptureGraphBuilder2* capture_graph_builder; /* Helps the Filter Graph Manager */
  IBaseFilter* capture_filter;    /* the capture device (can vary), it's a source filter. */
  IBaseFilter* grabber_filter;    /* returns the capture data, it's a transform filter */
  IBaseFilter* null_filter;       /* does nothing, act as a terminator, it's a rendering filter */
  ISampleGrabber* sample_grabber; /* Used to access the ISampleGrabber interface, since grabber_filter is a generic IBaseFilter interface based on ISampleGrabber. */
  IMediaControl* media_control;   /* Used to Run and Stop the graph flow. */
	IMediaEvent* media_event;       /* Used by the imVideoCaptureFrame function to wait for the data. */
  IAMVideoProcAmp* video_prop;    /* Used to set/get video properties */
  IAMCameraControl* camera_prop;  /* Used to set/get camera properties */
  IAMVideoControl* videoctrl_prop; /* Used to set/get video properties */
};

char* imVideoCaptureDeviceDesc(int device)
{
  // List available Devices once
  if (vc_DeviceCount == 0)
  {
    vc_EnumerateDevices();

    if (vc_DeviceCount == 0)
      return NULL;
  }

  if (device >= vc_DeviceCount)
    return NULL;

  return vc_DeviceDesc[device];
}

#define SafeRelease(p) { if( (p) != 0 ) { (p)->Release(); (p)= NULL; } }

static int vc_InitCaptureGraphBuilder(imVideoCapture* vc)
{
  HRESULT hr = CoCreateInstance(CLSID_CaptureGraphBuilder2, NULL, CLSCTX_INPROC_SERVER, 
                                IID_ICaptureGraphBuilder2, reinterpret_cast<void**>(&vc->capture_graph_builder));
  if (FAILED(hr)) return 0; 

  hr = CoCreateInstance(CLSID_FilterGraph, NULL, CLSCTX_INPROC_SERVER,
                        IID_IGraphBuilder, reinterpret_cast<void**>(&vc->filter_builder));
  if (FAILED(hr)) return 0; 

  hr = CoCreateInstance(CLSID_SampleGrabber, NULL, CLSCTX_INPROC_SERVER, 
                        IID_IBaseFilter, reinterpret_cast<void**>(&vc->grabber_filter));
  if (FAILED(hr)) return 0; 

  hr = CoCreateInstance(CLSID_NullRenderer, NULL, CLSCTX_INPROC_SERVER, 
                        IID_IBaseFilter, reinterpret_cast<void**>(&vc->null_filter));
  if (FAILED(hr)) return 0; 

  // Initialize the Capture Graph Builder.
  vc->capture_graph_builder->SetFiltergraph(vc->filter_builder);

  hr = vc->filter_builder->QueryInterface(IID_IMediaControl,(void**)&vc->media_control);
	hr = vc->filter_builder->QueryInterface(IID_IMediaEvent, (void **)&vc->media_event);
  hr = vc->grabber_filter->QueryInterface(IID_ISampleGrabber, (void **)&vc->sample_grabber);

  AM_MEDIA_TYPE mt;
  ZeroMemory(&mt, sizeof(AM_MEDIA_TYPE));
  mt.majortype = MEDIATYPE_Video;
  mt.subtype = MEDIASUBTYPE_RGB24;  // Force 24 bpp
  mt.formattype = FORMAT_VideoInfo;
  vc->sample_grabber->SetMediaType(&mt);
  vc->sample_grabber->SetOneShot(FALSE);
  vc->sample_grabber->SetBufferSamples(TRUE);

  hr = vc->filter_builder->AddFilter(vc->grabber_filter, L"Grabber");
  hr = vc->filter_builder->AddFilter(vc->null_filter, L"NullRender");
  vc_ConnectFilters(vc->filter_builder, vc->grabber_filter, vc->null_filter);

  return 1;
}

imVideoCapture* imVideoCaptureCreate(void)
{
  imVideoCapture* vc = (imVideoCapture*)malloc(sizeof(imVideoCapture));
  memset(vc, 0, sizeof(imVideoCapture));

  // List available Devices once
  if (vc_DeviceCount == 0)
  {
    vc_EnumerateDevices();

    if (vc_DeviceCount == 0)
    {
      free(vc);
      return NULL;
    }
  }

  if (!vc_InitCaptureGraphBuilder(vc))
  {
    SafeRelease(vc->grabber_filter);
    SafeRelease(vc->filter_builder);
    SafeRelease(vc->capture_graph_builder);
    SafeRelease(vc->null_filter);
    free(vc);
    return NULL;
  }

  vc->device = -1;

  return vc;
}

void imVideoCaptureDestroy(imVideoCapture* vc)
{
  assert(vc);

  imVideoCaptureDisconnect(vc);

  SafeRelease(vc->sample_grabber);
  SafeRelease(vc->media_control);
  SafeRelease(vc->capture_filter);
  SafeRelease(vc->media_event);
  SafeRelease(vc->video_prop);
  SafeRelease(vc->camera_prop);
  SafeRelease(vc->videoctrl_prop);

  vc->null_filter->Release();
  vc->grabber_filter->Release();
  vc->filter_builder->Release();
  vc->capture_graph_builder->Release();

  free(vc);
}

int imVideoCaptureOneFrame(imVideoCapture* vc, unsigned char* data)
{
  assert(vc);
  assert(vc->device != -1);

  if (vc->live)
  {
    vc->media_control->Stop();
    Sleep(VC_CAMERADELAY);
    vc->live = 0;
  }

  vc->sample_grabber->SetOneShot(TRUE);
  if (FAILED(vc->media_control->Run()))
  {
    vc->sample_grabber->SetOneShot(FALSE);
    return 0;
  }
  vc->live = 1;

  Sleep(VC_CAMERADELAY);
  int ret = imVideoCaptureFrame(vc, data, -1);

  vc->media_control->Stop();
  vc->sample_grabber->SetOneShot(FALSE);
  vc->live = 0;
  return ret;
}

int imVideoCaptureFrame(imVideoCapture* vc, unsigned char* data, int timeout)
{
  assert(vc);
  assert(vc->device != -1);
  assert(vc->live);
  long evCode, cbBuffer = 0;
  HRESULT hr = vc->media_event->WaitForCompletion(timeout, &evCode);
  if (hr == E_ABORT) return 0;
  hr = vc->sample_grabber->GetCurrentBuffer(&cbBuffer, NULL);
  if (hr == VFW_E_WRONG_STATE)
  {
    Sleep(VC_CAMERADELAY);
    hr = vc->sample_grabber->GetCurrentBuffer(&cbBuffer, NULL);
  }
  hr = vc->sample_grabber->GetCurrentBuffer(&cbBuffer, (long*)data);
  return 1;
}

void imVideoCaptureGetImageSize(imVideoCapture* vc, int *width, int *height)
{
  assert(vc);
  assert(vc->device != -1);

  AM_MEDIA_TYPE mt;
  ZeroMemory(&mt, sizeof(AM_MEDIA_TYPE));
  HRESULT hr = vc->sample_grabber->GetConnectedMediaType(&mt);

  if ( SUCCEEDED(hr) &&
      (mt.majortype == MEDIATYPE_Video) &&
      (mt.formattype == FORMAT_VideoInfo) &&
      (mt.cbFormat >= sizeof (VIDEOINFOHEADER)) &&
      (mt.pbFormat != NULL))
  {
    VIDEOINFOHEADER *pVih = (VIDEOINFOHEADER*)mt.pbFormat;
    *width = pVih->bmiHeader.biWidth;
    *height = pVih->bmiHeader.biHeight;
    CoTaskMemFree((PVOID)mt.pbFormat);
  }
  else
  {
    *width = 0;
    *height = 0;
  }
}

int imVideoCaptureSetImageSize(imVideoCapture* vc, int width, int height)
{
  assert(vc);
  assert(vc->device != -1);

  IAMStreamConfig *pSC;
  HRESULT hr = vc->capture_graph_builder->FindInterface(&PIN_CATEGORY_CAPTURE,
                 &MEDIATYPE_Video, vc->capture_filter, IID_IAMStreamConfig, (void **)&pSC);

  AM_MEDIA_TYPE *pmt;
  hr = pSC->GetFormat(&pmt);

  VIDEOINFOHEADER* vih = (VIDEOINFOHEADER*)pmt->pbFormat;
  BITMAPINFOHEADER* bih = &vih->bmiHeader;

  /* dibs are DWORD aligned */
  int data_size = height * ((width * bih->biBitCount + 31) / 32) * 4;   /* 4 bytes boundary */

  bih->biSize = sizeof(BITMAPINFOHEADER);
  bih->biHeight = height;
  bih->biWidth = width;
  bih->biSizeImage = data_size;

  int fps = 30;  // desired frame rate
  vih->dwBitRate = fps * data_size;
  vih->AvgTimePerFrame = 10000000 / fps;

  pmt->cbFormat = sizeof(VIDEOINFOHEADER);
  pmt->lSampleSize = data_size;

  hr = pSC->SetFormat(pmt);
  pSC->Release();

  CoTaskMemFree((PVOID)pmt->pbFormat);
  CoTaskMemFree(pmt);

  return SUCCEEDED(hr);
}

void imVideoCaptureDisconnect(imVideoCapture* vc)
{
  assert(vc);

  if (vc->device == -1)
    return;

  if (vc->live)
  {
    vc->media_control->Stop();
    Sleep(VC_CAMERADELAY);
  }

  vc->filter_builder->RemoveFilter(vc->capture_filter);
  SafeRelease(vc->capture_filter);

  vc->live = 0;
  vc->device = -1;
}

int imVideoCaptureConnect(imVideoCapture* vc, int device)
{
  assert(vc);

  if (device == IMVC_QUERY)
    return vc->device;

  if (device == vc->device)
    return 1;

  if (vc->device != -1)
    imVideoCaptureDisconnect(vc);

  vc->capture_filter = vc_GetDevice(device);
  if (!vc->capture_filter)
    return 0;

  vc->filter_builder->AddFilter(vc->capture_filter, L"Capture");

  if (!vc_ConnectFilters(vc->filter_builder, vc->capture_filter, vc->grabber_filter))
    return 0;

  vc->device = device;

  return 1;
}

int imVideoCaptureLive(imVideoCapture* vc, int live)
{
  assert(vc);

  if (vc->device == -1)
    return 0;

  if (live == vc->live)
    return 1;

  if (live == IMVC_QUERY)
    return vc->live;

  HRESULT hr;
  if (live)
    hr = vc->media_control->Run(); 
  else
    hr = vc->media_control->Stop();

  if (FAILED(hr)) return 0;
  Sleep(VC_CAMERADELAY);

  vc->live = live;
  return 1;
}

int imVideoCaptureShowFormatDialog(imVideoCapture* vc, void* parent)
{
  assert(vc);
  assert(vc->device != -1);

  IAMVfwCaptureDialogs *pDlg;
  HRESULT hr = vc->capture_graph_builder->FindInterface(&PIN_CATEGORY_CAPTURE,
                 &MEDIATYPE_Video, vc->capture_filter, IID_IAMVfwCaptureDialogs, (void **)&pDlg);
  if(SUCCEEDED(hr))
  {
    int ret = -1;
    hr = pDlg->HasDialog(VfwCaptureDialog_Format);
    if (SUCCEEDED(hr))
    {
      ret = 0;
      if (vc->live)
      {
        vc->media_control->Stop();
        Sleep(VC_CAMERADELAY);
      }

      vc_DisconnectFilters(vc->filter_builder, vc->capture_filter, vc->grabber_filter);

      hr = pDlg->ShowDialog(VfwCaptureDialog_Format, (HWND)parent);
      if (SUCCEEDED(hr))
        ret = 1;

      if (!vc_ConnectFilters(vc->filter_builder, vc->capture_filter, vc->grabber_filter))
        ret = 0;

      if (vc->live)
      {
        vc->live = SUCCEEDED(vc->media_control->Run());
        Sleep(VC_CAMERADELAY);
      }
    }

    pDlg->Release();
    return ret;
  }

  IAMStreamConfig *pSC;
  hr = vc->capture_graph_builder->FindInterface(&PIN_CATEGORY_CAPTURE,
                 &MEDIATYPE_Video, vc->capture_filter, IID_IAMStreamConfig, (void **)&pSC);
  if (FAILED(hr)) return -1;

  if (vc->live)
  {
    vc->media_control->Stop();
    Sleep(VC_CAMERADELAY);
  }

  vc_DisconnectFilters(vc->filter_builder, vc->capture_filter, vc->grabber_filter);

  int ok = vc_ShowPropertyPages((HWND)parent, (IUnknown*)pSC, L"Video Capture Format");
  pSC->Release();

  if (!vc_ConnectFilters(vc->filter_builder, vc->capture_filter, vc->grabber_filter))
    ok = 0;

  if (vc->live)
  {
    vc->live = SUCCEEDED(vc->media_control->Run());
    Sleep(VC_CAMERADELAY);
  }

  return ok;
}

int imVideoCaptureShowSourceDialog(imVideoCapture* vc, void* parent)
{
  assert(vc);
  assert(vc->device != -1);

  IAMVfwCaptureDialogs *pDlg;
  HRESULT hr = vc->capture_graph_builder->FindInterface(&PIN_CATEGORY_CAPTURE,
                 &MEDIATYPE_Video, vc->capture_filter, IID_IAMVfwCaptureDialogs, (void **)&pDlg);
  if(SUCCEEDED(hr))
  {
    int ret = -1;
    hr = pDlg->HasDialog(VfwCaptureDialog_Source);
    if (SUCCEEDED(hr))
    {
      if (vc->live)
      {
        vc->media_control->Stop();
        Sleep(VC_CAMERADELAY);
      }

      ret = 0;
      hr = pDlg->ShowDialog(VfwCaptureDialog_Source, (HWND)parent);
      if (SUCCEEDED(hr))
        ret = 1;

      if (vc->live)
      {
        vc->live = SUCCEEDED(vc->media_control->Run());
        Sleep(VC_CAMERADELAY);
      }
    }

    pDlg->Release();
    return ret;
  }

  return vc_ShowPropertyPages((HWND)parent, (IUnknown*)vc->capture_filter, L"Video Capture Source");
}
#define UH_SIZE 101
#define UH_MULTIPLIER 31

/** Unique Hash index for a key
 * We use the hash function described in "The Pratice of Programming" of Kernighan & Pike. */
static int HashIndex(const char *key, int hash_size)
{
  unsigned short hash = 0;
  const unsigned char *p_key = (const unsigned char*)key;

  for(; *p_key; p_key++)
    hash = hash*UH_MULTIPLIER + *p_key;

  return hash % hash_size;
}

#define VC_CAMERASHIFT 20
#define VC_VIDEOSHIFT 40

static long vc_Attrib2Property(const char* attrib)
{
  static long prop_table[UH_SIZE];
  static int first = 1;
  if (first)
  {
    memset(prop_table, 0, UH_SIZE*sizeof(long));
    prop_table[HashIndex("CameraPanAngle", UH_SIZE)] = (long)CameraControl_Pan + VC_CAMERASHIFT + 1;
    prop_table[HashIndex("CameraTiltAngle", UH_SIZE)] = (long)CameraControl_Tilt + VC_CAMERASHIFT + 1;
    prop_table[HashIndex("CameraRollAngle", UH_SIZE)] = (long)CameraControl_Roll + VC_CAMERASHIFT + 1;
    prop_table[HashIndex("CameraLensZoom", UH_SIZE)] = (long)CameraControl_Zoom + VC_CAMERASHIFT + 1;
    prop_table[HashIndex("CameraExposure", UH_SIZE)] = (long)CameraControl_Exposure + VC_CAMERASHIFT + 1;
    prop_table[HashIndex("CameraIris", UH_SIZE)] = (long)CameraControl_Iris + VC_CAMERASHIFT + 1;
    prop_table[HashIndex("CameraFocus", UH_SIZE)] = (long)CameraControl_Focus + VC_CAMERASHIFT + 1;
    prop_table[HashIndex("VideoBrightness", UH_SIZE)] = (long)VideoProcAmp_Brightness + 1;
    prop_table[HashIndex("VideoContrast", UH_SIZE)] = (long)VideoProcAmp_Contrast + 1;
    prop_table[HashIndex("VideoHue", UH_SIZE)] = (long)VideoProcAmp_Hue + 1;
    prop_table[HashIndex("VideoSaturation", UH_SIZE)] = (long)VideoProcAmp_Saturation + 1;
    prop_table[HashIndex("VideoSharpness", UH_SIZE)] = (long)VideoProcAmp_Sharpness + 1;
    prop_table[HashIndex("VideoGamma", UH_SIZE)] = (long)VideoProcAmp_Gamma + 1;
    prop_table[HashIndex("VideoColorEnable", UH_SIZE)] = (long)VideoProcAmp_ColorEnable + 1;
    prop_table[HashIndex("VideoWhiteBalance", UH_SIZE)] = (long)VideoProcAmp_WhiteBalance + 1;
    prop_table[HashIndex("VideoBacklightCompensation", UH_SIZE)] = (long)VideoProcAmp_BacklightCompensation + 1;
    prop_table[HashIndex("VideoGain", UH_SIZE)] = (long)VideoProcAmp_Gain + 1;
    prop_table[HashIndex("FlipHorizontal", UH_SIZE)] = (long)VideoControlFlag_FlipHorizontal + VC_VIDEOSHIFT + 1;
    prop_table[HashIndex("FlipVertical", UH_SIZE)] = (long)VideoControlFlag_FlipVertical + VC_VIDEOSHIFT + 1;
    first = 0;
  }
  long prop = prop_table[HashIndex(attrib, UH_SIZE)];
  if (!prop)
    return 0;
  return prop-1;
}

int imVideoCaptureSetAttribute(imVideoCapture* vc, const char* attrib, double percent)
{
  assert(vc);
  assert(vc->device != -1);

  long property = vc_Attrib2Property(attrib);
  if (property == -1) return 0;
  if (property < VC_CAMERASHIFT)
    return vc_SetVideoProcAmpProperty(vc->capture_filter, &vc->video_prop, property, percent);
  else if (property < VC_VIDEOSHIFT)
    return vc_SetCameraControlProperty(vc->capture_filter, &vc->camera_prop, property-VC_CAMERASHIFT, percent);
  else
    return vc_SetVideoControlProperty(vc->capture_filter, &vc->videoctrl_prop, property-VC_VIDEOSHIFT, percent);
}

int imVideoCaptureGetAttribute(imVideoCapture* vc, const char* attrib, double *percent)
{
  assert(vc);
  assert(vc->device != -1);

  long property = vc_Attrib2Property(attrib);
  if (property == -1) return 0;
  if (property < VC_CAMERASHIFT)
    return vc_GetVideoProcAmpProperty(vc->capture_filter, &vc->video_prop, property, percent);
  else if (property < VC_VIDEOSHIFT)
    return vc_GetCameraControlProperty(vc->capture_filter, &vc->camera_prop, property-VC_CAMERASHIFT, percent);
  else
    return vc_GetVideoControlProperty(vc->capture_filter, &vc->videoctrl_prop, property-VC_VIDEOSHIFT, percent);
}

int imVideoCaptureResetAttribute(imVideoCapture* vc, const char* attrib, int fauto)
{
  assert(vc);
  assert(vc->device != -1);

  long property = vc_Attrib2Property(attrib);
  if (property == -1) return 0;
  if (property < VC_CAMERASHIFT)
    return vc_ResetVideoProcAmpProperty(vc->capture_filter, &vc->video_prop, property, fauto);
  else if (property < VC_VIDEOSHIFT)
    return vc_ResetCameraControlProperty(vc->capture_filter, &vc->camera_prop, property-VC_CAMERASHIFT, fauto);
  else
    return vc_ResetVideoControlProperty(vc->capture_filter, &vc->videoctrl_prop, property-VC_VIDEOSHIFT, fauto);
}

/*
Pre-calcultated Hash Index:

VideoBrightness (97) - Specifies the brightness, also called the black level. 
VideoContrast (80) - Specifies the contrast, expressed as gain factor. 
VideoHue (98) - Specifies the hue angle. 
VideoSaturation (4) - Specifies the saturation.
VideoSharpness (56) - Specifies the sharpness. 
VideoGamma (67) - Specifies the gamma. 
VideoColorEnable (91) - Specifies the color enable setting. (0/100)
VideoWhiteBalance (26) - Specifies the white balance, as a color temperature in degrees Kelvin. 
VideoBacklightCompensation (50) - Specifies the backlight compensation setting. (0/100)
VideoGain (36) - Specifies the gain adjustment.
CameraPanAngle (64) - Specifies the camera's pan angle. To 100 rotate right, To 0 rotate left (view from above).
CameraTiltAngle (54) - Specifies the camera's tilt angle.  To 100 rotate up, To 0 rotate down.
CameraRollAngle (85) - Specifies the camera's roll angle. To 100 rotate right, To 0 rotate left.
CameraLensZoom (57) - Specifies the camera's zoom setting. 
CameraExposure (84) - Specifies the exposure setting. 
CameraIris (20) - Specifies the camera's iris setting. 
CameraFocus (62) - Specifies the camera's focus setting, as the distance to the optimally focused target. 
FlipHorizontal (21)
FlipVertical (28)
*/


