/*
 * Module to grab video data from VfW interface.  The interface is
 * poorly documented for which some odd comments maybe found here.
 */

#include "config.h"

#include <windows.h>
#include <windowsx.h>
#include <vfw.h>
#include <aviriff.h>

#include <list>
#include <string>

#include "convert.h"
#include "videodev-vfw.h"

static char classNameVfW[] = "VFW-Class";

/*
 * Constructor
 */
VideoDev_VFW::VideoDev_VFW() {
  printf ("%s:%d %s\n", __FILE__, __LINE__, __FUNCTION__);
  hwnd = cap = NULL;
  camid = -1;
  inframe_pixfmt = 0x0;
  inframe_w = -1;
  inframe_h = -1;
  inframe = NULL;
  inframe_size = 0;
  vfw_size = 0;
  hclass = NULL;
  hinst = NULL;
};

/*
 * Destructor
 */
VideoDev_VFW::~VideoDev_VFW() {
  printf ("%s:%d %s\n", __FILE__, __LINE__, __FUNCTION__);
  if (running > 0)
 	CaptureStop();
}

/*
 * Check for connected devices and returns the result in a list.
 */
int VideoDev_VFW::GetDeviceList(std::list<std::string> *list) {

  char name[256];
  char desc[256];
    
  printf ("%s:%d %s\n", __FILE__, __LINE__, __FUNCTION__);
  if (list == NULL) return VDEV_STATUS_ERROR;

  // go through a list of 10 (maximum number defined by VfW)
  for(int i=0; i < 10; i++)
    if(capGetDriverDescription(i, name, sizeof(name), desc, sizeof(desc))) {
      // create a driver for the return list
      std::string device;
      device = "VFW " + to_string(i) + " " + (string)name + " [" + (string)desc + "]";
      printf ("%s:%d %s Found device '%s'\n", __FILE__, __LINE__, __FUNCTION__, device.c_str());
      list->push_back(device);
    }

  return VDEV_STATUS_OK;
}

/*
 * Destroy and unregister window class.
 */
int VideoDev_VFW::DestroyClass() {
  printf ("%s:%d %s\n", __FILE__, __LINE__, __FUNCTION__);
  if (hclass) {
    if(!UnregisterClass(classNameVfW, hinst)) {
      printf ("%s:%d %s Could not unregister VFW class\n", __FILE__, __LINE__, __FUNCTION__);
      return 0;
    }
    free (hclass);
    hclass = NULL;
  }
  return 1;
}

/*
 * Create and register window class.
 */
int VideoDev_VFW::CreateClass() {

  printf ("%s:%d %s\n", __FILE__, __LINE__, __FUNCTION__);
  if (hclass) return 1;
  
  hclass = (WNDCLASSEX *)malloc(sizeof(WNDCLASSEX));
  hclass->hInstance = hinst;
  hclass->lpszClassName = classNameVfW;
  hclass->lpfnWndProc = DefWindowProc;
  hclass->style = CS_DBLCLKS;
  hclass->cbSize = sizeof(WNDCLASSEX);
  hclass->hIcon = LoadIcon(NULL,IDI_APPLICATION);
  hclass->hIconSm = LoadIcon(NULL,IDI_APPLICATION);
  hclass->hCursor = LoadCursor(NULL,IDC_ARROW);
  hclass->lpszMenuName = NULL;
  hclass->cbClsExtra = 0;
  hclass->cbWndExtra = 0;
  hclass->hbrBackground = (HBRUSH)COLOR_BACKGROUND;

  if(!RegisterClassEx (hclass)) {
    printf ("%s:%d %s Could not register VFW class\n", __FILE__, __LINE__, __FUNCTION__);
    return 0;
  }
  return 1;
}

/*
 * Print driver capabilities.
 */
void VideoDev_VFW::GetCapabilities() {
  
    CAPDRIVERCAPS caps;
    if(!capDriverGetCaps(cap, &caps, sizeof(caps))) {
      printf ("%s:%d %s Unable to get driver capabilities\n", __FILE__, __LINE__, __FUNCTION__);
    }
    else {
      printf ("%s:%d %s Id: %d\n", __FILE__, __LINE__, __FUNCTION__, caps.wDeviceIndex);
      printf ("%s:%d %s Overlay: %d\n", __FILE__, __LINE__, __FUNCTION__, caps.fHasOverlay);
      printf ("%s:%d %s VideoSource Dialog: %d\n", __FILE__, __LINE__, __FUNCTION__, caps.fHasDlgVideoSource);
      printf ("%s:%d %s VideoFormat Dialog: %d\n", __FILE__, __LINE__, __FUNCTION__, caps.fHasDlgVideoFormat);
      printf ("%s:%d %s VideoDisplay Dialog: %d\n", __FILE__, __LINE__, __FUNCTION__, caps.fHasDlgVideoDisplay);
    }
}  

/*
 * The callback is run when an error in the capture driver occurred.
 */
LRESULT VFW_error_callback (HWND h, int nID, LPCSTR lpsz) {
  printf("%s:%d %s id=%d (%s)\n", __FILE__, __LINE__, __FUNCTION__, nID, lpsz);
  return TRUE;
}

/*
 * The callback is run when a status update in the capture driver occurred.
 */
LRESULT VFW_status_callback (HWND h, int nID, LPCSTR lpsz) {
  printf("%s:%d %s id=%d (%s)\n", __FILE__, __LINE__, __FUNCTION__, nID, lpsz);
  return TRUE;
}

#define USE_PREVIEW 0

/*
 * Opens the device.
 */
int VideoDev_VFW::Open() {

  printf ("%s:%d %s\n", __FILE__, __LINE__, __FUNCTION__);

  // extract camera id from device name
  camid = atoi (conf_device.c_str());
  printf ("%s:%d %s Opening VFW id %d\n", __FILE__, __LINE__, __FUNCTION__, camid);

  // find application instance handle for later use
  hinst = GetModuleHandle(NULL);

  // create and register window class
  if (!CreateClass())
    return VDEV_STATUS_ERROR;

  // now create the parent window for capture devicey
  // some notes:
  // - the minimum possible size of 3x3
  // - the WS_POPUPWINDOW removes window decorations
  // - it does not need a parent window
  // - the window MUST be visible, otherwise video grabbing does not work (no callbacks run)
#if USE_PREVIEW
  hwnd = CreateWindowEx(0, classNameVfW, "CameraPreview", WS_POPUPWINDOW|WS_VISIBLE, 0, 0, 3, 3, NULL, NULL, hinst, NULL);
#else
  hwnd = CreateWindowEx(0, classNameVfW, "CameraPreview", WS_POPUPWINDOW|WS_VISIBLE, 0, 0, 0, 0, NULL, NULL, hinst, NULL);
#endif
  if (!hwnd) {
    printf ("%s:%d %s Could not create window (%ld)\n", __FILE__, __LINE__, __FUNCTION__, GetLastError());
    return VDEV_STATUS_ERROR;
  }
#if USE_PREVIEW
  if(!ShowWindow(hwnd, SW_SHOW)) {
    printf ("%s:%d %s Could not show window (%ld)\n", __FILE__, __LINE__, __FUNCTION__, GetLastError());
    return VDEV_STATUS_ERROR;
  }
#else
  if(!ShowWindow(hwnd, SW_HIDE)) {
    printf ("%s:%d %s Could not hide window (%ld)\n", __FILE__, __LINE__, __FUNCTION__, GetLastError());
    return VDEV_STATUS_ERROR;
  }
#endif

  // create capture driver window handle, also here minim size is 1x1
#if USE_PREVIEW
  cap = capCreateCaptureWindow("VFW", WS_CHILD|WS_VISIBLE, 0, 0, 1, 1, hwnd, camid);
#else
  cap = capCreateCaptureWindow("VFW", WS_CHILD, 0, 0, 0, 0, hwnd, camid);
#endif  
  if(!cap) {
    printf ("%s:%d %s Could not open VFW id %d window\n", __FILE__, __LINE__, __FUNCTION__, camid);
    return VDEV_STATUS_ERROR;
  }
  if(!capSetCallbackOnStatus(cap, VFW_status_callback)) {
    printf ("%s:%d %s Could not set status callback to VFW id %d (%ld)\n", __FILE__, __LINE__, __FUNCTION__, camid, GetLastError());
    return VDEV_STATUS_ERROR;
  }
  if(!capSetCallbackOnError(cap, VFW_error_callback)) {
    printf ("%s:%d %s Could not set error callback to VFW id %d (%ld)\n", __FILE__, __LINE__, __FUNCTION__, camid, GetLastError());
    return VDEV_STATUS_ERROR;
  }

  // connect to driver
  if(!capDriverConnect(cap, camid)) {
    printf ("%s:%d %s Could not connect to VFW id %d\n", __FILE__, __LINE__, __FUNCTION__, camid);
    return VDEV_STATUS_ERROR;
  }

  // check capabilities
  GetCapabilities();

  // let the callbacks know this class pointer
  capSetUserData(cap, this);

  // set video source, capture format and size
  //capDlgVideoSource(cap);
  //capDlgVideoFormat(cap);

  CAPTUREPARMS cp;
  if(!capCaptureGetSetup(cap, &cp, sizeof(cp))) {
    printf ("%s:%d %s Could not get VFW capture setup\n", __FILE__, __LINE__, __FUNCTION__);
    return VDEV_STATUS_ERROR;
  }
  cp.dwRequestMicroSecPerFrame = 10000; // rate in us
  cp.fMakeUserHitOKToCapture = 0;
  cp.wPercentDropForError = 10;
  cp.fYield = TRUE;
  cp.wNumVideoRequested = 1;
  cp.fCaptureAudio = 0;
  cp.vKeyAbort = 0;
  cp.fAbortLeftMouse = 0;
  cp.fAbortRightMouse = 0;
  cp.fLimitEnabled = 0;
  if(!capCaptureSetSetup(cap, &cp, sizeof(cp))) {
    printf ("%s:%d %s Could not set VFW capture setup\n", __FILE__, __LINE__, __FUNCTION__);
    return VDEV_STATUS_ERROR;
  }

  // get current valid width/height
  BITMAPINFO bi;
  int i = 0;
  capGetVideoFormat(cap, &bi, sizeof(BITMAPINFO));
  inframe_w = bi.bmiHeader.biWidth;
  inframe_h = bi.bmiHeader.biHeight;
  printf ("%s:%d %s Current capture size is %dx%d\n", __FILE__, __LINE__, __FUNCTION__, inframe_w, inframe_h);
  conf_format = convert_from_pixelformat(bi.bmiHeader.biCompression);
  inframe_pixfmt = bi.bmiHeader.biCompression;
  printf ("%s:%d %s Current capture format is %s\n", __FILE__, __LINE__, __FUNCTION__, conf_format.c_str());

  // try to set the configured width/height
  if(conf_width != -1) {
    inframe_w = bi.bmiHeader.biWidth = conf_width;
    inframe_h = bi.bmiHeader.biHeight = conf_height;
    bi.bmiHeader.biCompression = inframe_pixfmt;
    if(!capSetVideoFormat(cap, &bi, sizeof(BITMAPINFO))) {
      printf ("%s:%d %s Could not set capture size %dx%d (%s)\n", __FILE__, __LINE__, __FUNCTION__, conf_width, conf_height, conf_format.c_str());
      return VDEV_STATUS_ERROR;
    }
  }
  
  // find out maximum resolution
  else {
    int sizes[][2] = {{320, 240}, {640, 480}, {800, 600}, {1024, 768}, {1280, 720}, {1280, 1024}, {1920, 1080}, {-1, -1}};

    while(sizes[i][0] != -1) {
      inframe_w = bi.bmiHeader.biWidth = sizes[i][0];
      inframe_h = bi.bmiHeader.biHeight = sizes[i][1];
      bi.bmiHeader.biCompression = inframe_pixfmt;
      if(capSetVideoFormat(cap, &bi, sizeof(BITMAPINFO))) {
        printf ("%s:%d %s Resolution %dx%d (%s) works\n", __FILE__, __LINE__, __FUNCTION__, inframe_w, inframe_h, conf_format.c_str());
        if (inframe_w >= conf_width && inframe_h >= conf_height) {
          conf_width = inframe_w;
          conf_height = inframe_h;
        }
      }
      i++;
    }
  }

  // translate this special format to what our convert functions can work with
  if (inframe_pixfmt == V4L2_PIX_FMT_YUY2) {
    inframe_pixfmt = V4L2_PIX_FMT_YUYV;
  }
  
  return VDEV_STATUS_OK;
};


/*
 * Close the device.
 */
int VideoDev_VFW::Close() {

	printf ("%s:%d %s\n", __FILE__, __LINE__, __FUNCTION__);
	if(cap) {
	  capDriverDisconnect(cap);
	  DestroyWindow(cap);
	  DestroyWindow(hwnd);
	  hwnd = cap = NULL;
	}
    DestroyClass();
	return VDEV_STATUS_OK;
};



/*****************************************************************************************************
 * VideoGrabbing
 */

/*
 * The callback is run when a new frame is available.  It copies the
 * available data into the framebuffer of the class instance.
 */
LRESULT CALLBACK VFW_frame_callback (HWND h, LPVIDEOHDR v) {
  VideoDev_VFW * obj = (VideoDev_VFW *)capGetUserData(h);
  obj->SetFrameBufferSize(v->dwBytesUsed);
  if (obj->GetFrameBuffer()) {
    memcpy(obj->GetFrameBuffer(), v->lpData, obj->GetFrameBufferSize());
  }
  return TRUE;
}

/*
 * Prepare inframe for raw picture data, will hold a video frame with
 * maximum size of inframe size = 4*W*H.  Setup capture
 * callbacks. Then send the start capture signal to the camera.
 */
int VideoDev_VFW::CaptureStart() {

	printf ("%s:%d %s\n", __FILE__, __LINE__, __FUNCTION__);

	// allocate memory for frame data
	if (inframe != NULL) free (inframe);
	inframe_size = 4 * inframe_w * inframe_h;
	inframe = (unsigned char *) malloc(inframe_size);

	pixelformat = inframe_pixfmt;

#if USE_PREVIEW
	if(!capSetCallbackOnFrame(cap, VFW_frame_callback)) {
	  printf ("%s:%d %s Could not set frame callback to VFW id %d (%ld)\n", __FILE__, __LINE__, __FUNCTION__, camid, GetLastError());
	  return VDEV_STATUS_ERROR;
	}
#else
	if(!capSetCallbackOnVideoStream(cap, VFW_frame_callback)) {
	  printf ("%s:%d %s Could not set videostream callback to VFW id %d (%ld)\n", __FILE__, __LINE__, __FUNCTION__, camid, GetLastError());
	  return VDEV_STATUS_ERROR;
	}
#endif

    // disable overlay
    capOverlay(cap, FALSE);

#if USE_PREVIEW
    // use preview window for grabbing
    capPreviewRate (cap, 10); // rate in ms
    capPreviewScale (cap, FALSE);
    capPreview (cap, TRUE);
#else
    // otherwise use other capture
    capPreview (cap, FALSE);
    if(!capCaptureSequenceNoFile (cap)) {
      printf ("%s:%d %s Could not start capture (%ld)\n", __FILE__, __LINE__, __FUNCTION__, GetLastError());
    }
#endif

	return VDEV_STATUS_OK;
};



/*
 * Stop capture and free framebuffer.
 */
int VideoDev_VFW::CaptureStop() {

	printf ("%s:%d %s\n", __FILE__, __LINE__, __FUNCTION__);
    
	capCaptureAbort(cap);
	capSetCallbackOnFrame(cap, NULL);
	capSetCallbackOnVideoStream(cap, NULL);
	capSetCallbackOnStatus(cap, NULL);
	capSetCallbackOnError(cap, NULL);

	// free inframe memory
	if (inframe) {
		free (inframe);
		inframe_size = 0;
		inframe = NULL;
	}

	return VDEV_STATUS_OK;
};

/*
 * This function needed to continue running the capture window.
 */
void VideoDev_VFW::HandleMessages() {
  MSG msg;
  while(PeekMessage(&msg,NULL,0,0,PM_REMOVE)) {
    TranslateMessage(&msg);
    DispatchMessage(&msg);
  }
}


/*
 * Try to grab one frame and copy it into raw video buffer.  If
 * something goes wrong return an error code.  Return code
 * VDEV_STATUS_AGAIN is not an error. There was no video image ready
 * to read.
 */
int VideoDev_VFW::Grab(VideoFrameRaw *vf) {

    // Do not know exactly why, but needed to translate/dispatch window message
    HandleMessages();
    
    if (inframe == NULL) return VDEV_STATUS_ERROR;

	if (GetFrameBufferSize() > 0) {
	  LockMutex();
	  vf->CopyFrom(inframe_pixfmt, inframe_w, inframe_h, GetFrameBufferSize(), inframe);
      SetFrameBufferSize(0);
	  UnLockMutex();
	}
    else {
      return VDEV_STATUS_AGAIN;
    }
	
	return VDEV_STATUS_OK;
}

/*
 * For VfW this seems difficult / impossible to obtain the supported
 * video formats, but they must be selected via capDlgVideoFormat().
 */
int VideoDev_VFW::GetDeviceFormats(string device, std::list<string> *formats) {

	printf ("%s:%d %s\n", __FILE__, __LINE__, __FUNCTION__);
    return VDEV_STATUS_OK;
}



/*****************************************************************************************************
 * Controls
 */

/*
 * set video control identified by id
 */
int VideoDev_VFW::SetDevCtrl(unsigned int id, int value) {
	return VDEV_STATUS_OK;
};


/*
 * get video control identified by id
 */
int VideoDev_VFW::GetDevCtrl(unsigned int id, int *value) {
	return VDEV_STATUS_OK;
};

