// SPDX-FileCopyrightText: Copyright (c) Ken Martin, Will Schroeder, Bill Lorensen
// SPDX-License-Identifier: BSD-3-Clause
#include "vtkWin32VideoSource.h"

#include "vtkObjectFactory.h"
#include "vtkTimerLog.h"
#include "vtkUnsignedCharArray.h"

#include <cctype>

// because of warnings in windows header push and pop the warning level
#ifdef _MSC_VER
#pragma warning(push, 3)
#endif

#include "vtkWindows.h"
#include <vfw.h>
#include <winuser.h>

#ifdef _MSC_VER
#pragma warning(pop)
#endif

VTK_ABI_NAMESPACE_BEGIN
class vtkWin32VideoSourceInternal
{
public:
  vtkWin32VideoSourceInternal() {}
  HWND CapWnd;
  HWND ParentWnd;
  CAPSTATUS CapStatus;
  CAPDRIVERCAPS CapDriverCaps;
  CAPTUREPARMS CaptureParms;
  LPBITMAPINFO BitMapPtr;
};

// VFW compressed formats are listed at http://www.webartz.com/fourcc/
#define VTK_BI_UYVY 0x59565955

vtkStandardNewMacro(vtkWin32VideoSource);

//------------------------------------------------------------------------------
vtkWin32VideoSource::vtkWin32VideoSource()
{
  this->Internal = new vtkWin32VideoSourceInternal;
  this->Initialized = 0;

  this->FrameRate = 30;
  this->OutputFormat = VTK_RGB;
  this->NumberOfScalarComponents = 3;
  this->FrameBufferBitsPerPixel = 24;
  this->FlipFrames = 0;
  this->FrameBufferRowAlignment = 4;

  this->Internal->CapWnd = nullptr;
  this->Internal->ParentWnd = nullptr;
  this->BitMapSize = 0;
  this->Internal->BitMapPtr = nullptr;
  this->WndClassName[0] = '\0';

  this->Preview = 0;
}

//------------------------------------------------------------------------------
vtkWin32VideoSource::~vtkWin32VideoSource()
{
  this->vtkWin32VideoSource::ReleaseSystemResources();

  delete[] (char*)(this->Internal->BitMapPtr);
  this->Internal->BitMapPtr = nullptr;
  this->BitMapSize = 0;
  delete this->Internal;
}

//------------------------------------------------------------------------------
void vtkWin32VideoSource::PrintSelf(ostream& os, vtkIndent indent)
{
  this->Superclass::PrintSelf(os, indent);

  os << indent << "Preview: " << (this->Preview ? "On\n" : "Off\n");
}

//------------------------------------------------------------------------------
// This is empty for now because we aren't displaying the capture window
LONG FAR PASCAL vtkWin32VideoSourceWinProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
  vtkWin32VideoSource* self = (vtkWin32VideoSource*)(vtkGetWindowLong(hwnd, vtkGWL_USERDATA));

  switch (message)
  {

    case WM_MOVE:
      // cerr << "WM_MOVE\n";
      break;

    case WM_SIZE:
      // cerr << "WM_SIZE\n";
      break;

    case WM_DESTROY:
      // cerr << "WM_DESTROY\n";
      self->OnParentWndDestroy();
      break;

    case WM_CLOSE:
      // cerr << "WM_CLOSE\n";
      self->PreviewOff();
      return 0;
  }

  return (DefWindowProc(hwnd, message, wParam, lParam));
}

//------------------------------------------------------------------------------
LRESULT PASCAL vtkWin32VideoSourceCapControlProc(HWND hwndC, int nState)
{
  vtkWin32VideoSource* self = (vtkWin32VideoSource*)(capGetUserData(hwndC));

  if (nState == CONTROLCALLBACK_PREROLL)
  {
    // cerr << "controlcallback preroll\n";
    self->SetStartTimeStamp(vtkTimerLog::GetUniversalTime());
  }
  else if (nState == CONTROLCALLBACK_CAPTURING)
  {
    // cerr << "controlcallback capturing\n";
  }

  return TRUE;
}

//------------------------------------------------------------------------------
LRESULT PASCAL vtkWin32VideoSourceCallbackProc(HWND hwndC, LPVIDEOHDR lpVHdr)
{
  vtkWin32VideoSource* self = (vtkWin32VideoSource*)(capGetUserData(hwndC));
  self->LocalInternalGrab(lpVHdr);

  return 0;
}

//------------------------------------------------------------------------------
// this callback is left in for debug purposes
LRESULT PASCAL vtkWin32VideoSourceStatusCallbackProc(
  HWND vtkNotUsed(hwndC), int nID, LPCSTR vtkNotUsed(lpsz))
{
  // vtkWin32VideoSource *self = (vtkWin32VideoSource *)(capGetUserData(hwndC));

  if (nID == IDS_CAP_BEGIN)
  {
    // cerr << "start of capture\n";
  }

  if (nID == IDS_CAP_END)
  {
    // cerr << "end of capture\n";
  }

  return 1;
}

//------------------------------------------------------------------------------
LRESULT PASCAL vtkWin32VideoSourceErrorCallbackProc(HWND hwndC, int ErrID, LPSTR lpErrorText)
{
  if (ErrID)
  {
    char buff[84];
    snprintf(buff, sizeof(buff), "Error# %d", ErrID);
    MessageBox(hwndC, lpErrorText, buff, MB_OK | MB_ICONEXCLAMATION);
    // vtkGenericWarningMacro(<< buff << ' ' << lpErrorText);
  }
  return 1;
}

//------------------------------------------------------------------------------
void vtkWin32VideoSource::Initialize()
{
  int i;

  if (this->Initialized)
  {
    return;
  }

  // Preliminary update of frame buffer, just in case we don't get
  // though the initialization but need the framebuffer for Updates
  this->UpdateFrameBuffer();

  // It is necessary to create not one, but two windows in order to
  // do frame grabbing under VFW.  Why do we need any?

  // get necessary process info
  HINSTANCE hinstance = GetModuleHandle(nullptr);

  strcpy(this->WndClassName, "VTKVideo");

  // set up a class for the main window
  WNDCLASSA wc;
  wc.lpszClassName = this->WndClassName;
  wc.hInstance = hinstance;
  wc.lpfnWndProc = reinterpret_cast<WNDPROC>(&vtkWin32VideoSourceWinProc);
  wc.hCursor = LoadCursor(nullptr, IDC_ARROW);
  wc.hIcon = nullptr;
  wc.lpszMenuName = nullptr;
  wc.hbrBackground = nullptr;
  wc.style = CS_HREDRAW | CS_VREDRAW;
  wc.cbClsExtra = sizeof(void*);
  wc.cbWndExtra = 0;

  for (i = 1; i <= 10; i++)
  {
    if (RegisterClassA(&wc))
    {
      break;
    }
    // try again with a slightly different name
    snprintf(this->WndClassName, 16, "VTKVideo %d", i);
  }

  if (i > 10)
  {
    vtkErrorMacro(<< "Initialize: failed to register VTKVideo class"
                  << " (" << GetLastError() << ")");
    return;
  }

  DWORD style = WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU | WS_CLIPCHILDREN | WS_CLIPSIBLINGS;

  if (this->Preview)
  {
    style |= WS_VISIBLE;
  }

  // set up the parent window, but don't show it
  RECT r;
  r.left = 0;
  r.top = 0;
  r.right = this->FrameSize[0];
  r.bottom = this->FrameSize[1];
  BOOL result = AdjustWindowRect(&r, style, FALSE);
  if (!result)
  {
    vtkWarningMacro("Initialize: AdjustWindowRect failed, error: " << GetLastError());
  }

  this->Internal->ParentWnd = CreateWindowA(this->WndClassName, "VTK Video Window", style, 0, 0,
    r.right - r.left, r.bottom - r.top, nullptr, nullptr, hinstance, nullptr);

  if (!this->Internal->ParentWnd)
  {
    vtkErrorMacro(<< "Initialize: failed to create window"
                  << " (" << GetLastError() << ")");
    return;
  }

  // set the user data to 'this'
  vtkSetWindowLong(this->Internal->ParentWnd, vtkGWL_USERDATA, (vtkLONG)this);

  // Create the capture window
  this->Internal->CapWnd = capCreateCaptureWindow("Capture", WS_CHILD | WS_VISIBLE, 0, 0,
    this->FrameSize[0], this->FrameSize[1], this->Internal->ParentWnd, 1);

  if (!this->Internal->CapWnd)
  {
    vtkErrorMacro(<< "Initialize: failed to create capture window"
                  << " (" << GetLastError() << ")");
    this->ReleaseSystemResources();
    return;
  }

  // connect to the driver
  if (!capDriverConnect(this->Internal->CapWnd, 0))
  {
    vtkErrorMacro(<< "Initialize: couldn't connect to driver"
                  << " (" << GetLastError() << ")");
    this->ReleaseSystemResources();
    return;
  }

  capDriverGetCaps(this->Internal->CapWnd, &this->Internal->CapDriverCaps, sizeof(CAPDRIVERCAPS));

  // set up the video format
  this->DoVFWFormatSetup();

  // set the capture parameters
  capCaptureGetSetup(this->Internal->CapWnd, &this->Internal->CaptureParms, sizeof(CAPTUREPARMS));

  if (this->FrameRate > 0)
  {
    this->Internal->CaptureParms.dwRequestMicroSecPerFrame = int(1000000 / this->FrameRate);
  }
  else
  {
    this->Internal->CaptureParms.dwRequestMicroSecPerFrame = 0;
  }

  this->Internal->CaptureParms.fMakeUserHitOKToCapture = FALSE;
  this->Internal->CaptureParms.fYield = 1;
  this->Internal->CaptureParms.fCaptureAudio = FALSE;
  this->Internal->CaptureParms.vKeyAbort = 0x00;
  this->Internal->CaptureParms.fAbortLeftMouse = FALSE;
  this->Internal->CaptureParms.fAbortRightMouse = FALSE;
  this->Internal->CaptureParms.fLimitEnabled = FALSE;
  this->Internal->CaptureParms.wNumAudioRequested = 0;
  this->Internal->CaptureParms.wPercentDropForError = 100;
  this->Internal->CaptureParms.dwAudioBufferSize = 0;
  this->Internal->CaptureParms.AVStreamMaster = AVSTREAMMASTER_NONE;

  if (!capCaptureSetSetup(
        this->Internal->CapWnd, &this->Internal->CaptureParms, sizeof(CAPTUREPARMS)))
  {
    vtkErrorMacro(<< "Initialize: setup of capture parameters failed"
                  << " (" << GetLastError() << ")");
    this->ReleaseSystemResources();
    return;
  }

  // set user data for callbacks
  if (!capSetUserData(this->Internal->CapWnd, this))
  {
    vtkErrorMacro(<< "Initialize: couldn't set user data for callback"
                  << " (" << GetLastError() << ")");
    this->ReleaseSystemResources();
    return;
  }

  // install the callback to precisely time beginning of grab
  if (!capSetCallbackOnCapControl(this->Internal->CapWnd, &vtkWin32VideoSourceCapControlProc))
  {
    vtkErrorMacro(<< "Initialize: couldn't set control callback"
                  << " (" << GetLastError() << ")");
    this->ReleaseSystemResources();
    return;
  }

  // install the callback to copy frames into the buffer on sync grabs
  if (!capSetCallbackOnFrame(this->Internal->CapWnd, &vtkWin32VideoSourceCallbackProc))
  {
    vtkErrorMacro(<< "Initialize: couldn't set frame callback"
                  << " (" << GetLastError() << ")");
    this->ReleaseSystemResources();
    return;
  }
  // install the callback to copy frames into the buffer on stream grabs
  if (!capSetCallbackOnVideoStream(this->Internal->CapWnd, &vtkWin32VideoSourceCallbackProc))
  {
    vtkErrorMacro(<< "Initialize: couldn't set stream callback"
                  << " (" << GetLastError() << ")");
    this->ReleaseSystemResources();
    return;
  }
  // install the callback to get info on start/end of streaming
  if (!capSetCallbackOnStatus(this->Internal->CapWnd, &vtkWin32VideoSourceStatusCallbackProc))
  {
    vtkErrorMacro(<< "Initialize: couldn't set status callback"
                  << " (" << GetLastError() << ")");
    this->ReleaseSystemResources();
    return;
  }
  // install the callback to send messages to user
  if (!capSetCallbackOnError(this->Internal->CapWnd, &vtkWin32VideoSourceErrorCallbackProc))
  {
    vtkErrorMacro(<< "Initialize: couldn't set error callback"
                  << " (" << GetLastError() << ")");
    this->ReleaseSystemResources();
    return;
  }

  capOverlay(this->Internal->CapWnd, TRUE);

  // update framebuffer again to reflect any changes which
  // might have occurred
  this->UpdateFrameBuffer();

  this->Initialized = 1;
}

//------------------------------------------------------------------------------
void vtkWin32VideoSource::SetPreview(int p)
{
  if (this->Preview == p)
  {
    return;
  }

  this->Preview = p;
  this->Modified();

  if (this->Internal->CapWnd == nullptr || this->Internal->ParentWnd == nullptr)
  {
    return;
  }

  if (p)
  {
    ShowWindow(this->Internal->ParentWnd, SW_SHOWNORMAL);
  }
  else
  {
    ShowWindow(this->Internal->ParentWnd, SW_HIDE);
  }
}

//------------------------------------------------------------------------------
void vtkWin32VideoSource::ReleaseSystemResources()
{
  // destruction of ParentWnd causes OnParentWndDestroy to be called
  if (this->Internal->ParentWnd)
  {
    DestroyWindow(this->Internal->ParentWnd);
  }
}

//------------------------------------------------------------------------------
void vtkWin32VideoSource::OnParentWndDestroy()
{
  if (this->Playing || this->Recording)
  {
    this->Stop();
  }

  if (this->Internal->CapWnd)
  {
    // MessageBox(this->Internal->ParentWnd, "capDriverDisconnect(this->Internal->CapWnd)", "",
    // MB_OK | MB_ICONEXCLAMATION);
    capDriverDisconnect(this->Internal->CapWnd);
    // MessageBox(this->Internal->ParentWnd, "DestroyWindow(this->Internal->CapWnd)", "", MB_OK |
    // MB_ICONEXCLAMATION);
    DestroyWindow(this->Internal->CapWnd);
    this->Internal->CapWnd = nullptr;
  }
  if (this->WndClassName[0] != '\0')
  {
    UnregisterClass(this->WndClassName, GetModuleHandle(nullptr));
    this->WndClassName[0] = '\0';
  }

  this->Internal->ParentWnd = nullptr;
  this->Initialized = 0;
}

//------------------------------------------------------------------------------
// copy the Device Independent Bitmap from the VFW framebuffer into the
// vtkVideoSource framebuffer (don't do the unpacking yet)
void vtkWin32VideoSource::LocalInternalGrab(void* lpptr)
{
  LPVIDEOHDR lpVHdr = static_cast<LPVIDEOHDR>(lpptr);
  // cerr << "Grabbed\n";

  // the VIDEOHDR has the following contents, for quick ref:
  //
  // lpData                 pointer to locked data buffer
  // dwBufferLength         Length of data buffer
  // dwBytesUsed            Bytes actually used
  // dwTimeCaptured         Milliseconds from start of stream
  // dwUser                 for client's use
  // dwFlags                assorted flags (see VFW.H)
  // dwReserved[4]          reserved for driver

  unsigned char* cptrDIB = lpVHdr->lpData;

  // get a thread lock on the frame buffer
  this->FrameBufferMutex.lock();

  if (this->AutoAdvance)
  {
    this->AdvanceFrameBuffer(1);
    if (this->FrameIndex + 1 < this->FrameBufferSize)
    {
      this->FrameIndex++;
    }
  }

  int index = this->FrameBufferIndex;

  this->FrameCount++;
  this->FrameBufferTimeStamps[index] = this->StartTimeStamp + 0.001 * lpVHdr->dwTimeCaptured;

  unsigned char* ptr =
    (unsigned char*)((reinterpret_cast<vtkUnsignedCharArray*>(this->FrameBuffer[index]))
                       ->GetPointer(0));

  // the DIB has rows which are multiples of 4 bytes
  int outBytesPerRow =
    ((this->FrameBufferExtent[1] - this->FrameBufferExtent[0] + 1) * this->FrameBufferBitsPerPixel +
      7) /
    8;
  outBytesPerRow += outBytesPerRow % this->FrameBufferRowAlignment;
  int inBytesPerRow = this->FrameSize[0] * (this->Internal->BitMapPtr->bmiHeader.biBitCount / 8);
  outBytesPerRow += outBytesPerRow % 4;
  int rows = this->FrameBufferExtent[3] - this->FrameBufferExtent[2] + 1;

  cptrDIB += this->FrameBufferExtent[0] * (this->Internal->BitMapPtr->bmiHeader.biBitCount / 8);
  cptrDIB += this->FrameBufferExtent[2] * inBytesPerRow;

  // uncompress or simply copy the DIB
  switch (this->Internal->BitMapPtr->bmiHeader.biCompression)
  {
    case BI_RGB:
    case VTK_BI_UYVY:
      if (outBytesPerRow == inBytesPerRow)
      {
        memcpy(ptr, cptrDIB, inBytesPerRow * rows);
      }
      else
      {
        while (--rows >= 0)
        {
          memcpy(ptr, cptrDIB, outBytesPerRow);
          ptr += outBytesPerRow;
          cptrDIB += inBytesPerRow;
        }
      }
      break;
    case BI_RLE8: // not handled
    case BI_RLE4:
    case BI_BITFIELDS:
      break;
  }

  this->Modified();

  this->FrameBufferMutex.unlock();
}

//------------------------------------------------------------------------------
void vtkWin32VideoSource::Grab()
{
  if (this->Recording)
  {
    return;
  }

  // ensure that the frame buffer is properly initialized
  this->Initialize();
  if (!this->Initialized)
  {
    return;
  }

  // just do the grab, the callback does the rest
  this->SetStartTimeStamp(vtkTimerLog::GetUniversalTime());
  capGrabFrameNoStop(this->Internal->CapWnd);
}

//------------------------------------------------------------------------------
void vtkWin32VideoSource::Record()
{
  this->Initialize();
  if (!this->Initialized)
  {
    return;
  }

  if (this->Playing)
  {
    this->Stop();
  }

  if (!this->Recording)
  {
    this->Recording = 1;
    this->Modified();
    capCaptureSequenceNoFile(this->Internal->CapWnd);
  }
}

//------------------------------------------------------------------------------
void vtkWin32VideoSource::Play()
{
  this->vtkVideoSource::Play();
}

//------------------------------------------------------------------------------
void vtkWin32VideoSource::Stop()
{
  if (this->Recording)
  {
    this->Recording = 0;
    this->Modified();

    capCaptureStop(this->Internal->CapWnd);
  }
  else if (this->Playing)
  {
    this->vtkVideoSource::Stop();
  }
}

//------------------------------------------------------------------------------
// codecs

static inline void vtkYUVToRGB(unsigned char* yuv, unsigned char* rgb)
{
  /* // floating point
  int Y = yuv[0] - 16;
  int U = yuv[1] - 128;
  int V = yuv[2] - 128;

  int R = 1.164*Y + 1.596*V           + 0.5;
  int G = 1.164*Y - 0.813*V - 0.391*U + 0.5;
  int B = 1.164*Y           + 2.018*U + 0.5;
  */

  // integer math
  int Y = (yuv[0] - 16) * 76284;
  int U = yuv[1] - 128;
  int V = yuv[2] - 128;

  int R = Y + 104595 * V;
  int G = Y - 53281 * V - 25625 * U;
  int B = Y + 132252 * U;

  // round
  R += 32768;
  G += 32768;
  B += 32768;

  // shift
  R >>= 16;
  G >>= 16;
  B >>= 16;

  // clamp
  if (R < 0)
  {
    R = 0;
  }
  if (G < 0)
  {
    G = 0;
  }
  if (B < 0)
  {
    B = 0;
  }

  if (R > 255)
  {
    R = 255;
  }
  if (G > 255)
  {
    G = 255;
  }
  if (B > 255)
  {
    B = 255;
  }

  // output
  rgb[0] = R;
  rgb[1] = G;
  rgb[2] = B;
}

//------------------------------------------------------------------------------
void vtkWin32VideoSource::UnpackRasterLine(char* outptr, char* inptr, int start, int count)
{
  char alpha = (char)(this->Opacity * 255);
  int compression = this->Internal->BitMapPtr->bmiHeader.biCompression;
  int i;

  switch (this->FrameBufferBitsPerPixel)
  {
    case 1:
    {
      int rawBits;
      inptr += start / 8;
      i = start % 8;
      while (count >= 0)
      {
        rawBits = *inptr++;
        for (; i < 8 && --count >= 0; i++)
        {
          *outptr++ = -((rawBits >> i) & 0x01);
        }
        i = 0;
      }
    }
    break;
    case 4:
    {
      int rawNibbles;
      inptr += start / 2;
      i = start % 2;
      while (count >= 0)
      {
        rawNibbles = *inptr++;
        for (; i < 8 && --count >= 0; i += 4)
        {
          *outptr++ = ((rawNibbles >> i) & 0x0f) << 4;
        }
        i = 0;
      }
    }
    break;
    case 8:
    {
      inptr += start;
      memcpy(outptr, inptr, count);
    }
    break;
    case 16:
    {
      inptr += 2 * start;
      if (compression == VTK_BI_UYVY)
      {
        switch (this->OutputFormat)
        {
          case VTK_LUMINANCE:
          { // unpack UY half-megapixel to one Y pixel
            while (--count >= 0)
            {
              inptr++;
              *outptr++ = *inptr++;
            }
          }
          case VTK_RGB:
          case VTK_RGBA:
          { // unpack UYVY megapixel to two RGB or RGBA pixels
            unsigned char YUV[3];
            // int finish = start + count;
            int odd = (start % 2 == 1);
            if (count > 0)
            {
              YUV[1 + odd] = inptr[0];
            }
            if (count > 1)
            {
              YUV[0] = inptr[1];
            }
            if (count > 2)
            {
              YUV[2 - odd] = inptr[2];
            }
            while (--count >= 0)
            {
              YUV[1 + odd] = *inptr++;
              YUV[0] = *inptr++;
              odd = !odd;
              vtkYUVToRGB(YUV, (unsigned char*)outptr);
              outptr += 3;
              if (this->OutputFormat == VTK_RGB)
              {
                continue;
              }
              *outptr++ = alpha;
            }
          }
        }
      }
      else
      {
        unsigned short rawWord;
        unsigned short* shptr = (unsigned short*)inptr;
        switch (this->OutputFormat)
        {
          case VTK_RGB:
          { // unpack 16 bits to 24 bits
            while (--count >= 0)
            {
              rawWord = *shptr++;
              *outptr++ = (rawWord & 0x7c00) >> 7;
              *outptr++ = (rawWord & 0x03e0) >> 2;
              *outptr++ = (rawWord & 0x001f) << 3;
            }
          }
          break;
          case VTK_RGBA:
          { // unpack 16 bits to 32 bits
            while (--count >= 0)
            {
              rawWord = *shptr++;
              *outptr++ = (rawWord & 0x7c00) >> 7;
              *outptr++ = (rawWord & 0x03e0) >> 2;
              *outptr++ = (rawWord & 0x001f) << 3;
              *outptr++ = alpha;
            }
            break;
          }
        }
      }
    }
    case 24:
    {
      inptr += 3 * start;
      switch (this->OutputFormat)
      {
        case VTK_RGB:
        { // must do BGR to RGB conversion
          outptr += 3;
          while (--count >= 0)
          {
            *--outptr = *inptr++;
            *--outptr = *inptr++;
            *--outptr = *inptr++;
            outptr += 6;
          }
        }
        break;
        case VTK_RGBA:
        { // must do BGR to RGBX conversion
          outptr += 4;
          while (--count >= 0)
          {
            *--outptr = alpha;
            *--outptr = *inptr++;
            *--outptr = *inptr++;
            *--outptr = *inptr++;
            outptr += 8;
          }
        }
        break;
      }
    }
    break;
    case 32:
      inptr += 4 * start;
      switch (this->OutputFormat)
      {
        case VTK_RGB:
        { // must do BGRX to RGB conversion
          outptr += 3;
          while (--count >= 0)
          {
            *--outptr = *inptr++;
            *--outptr = *inptr++;
            *--outptr = *inptr++;
            inptr++;
            outptr += 6;
          }
        }
        break;
        case VTK_RGBA:
        {
          outptr += 4;
          while (--count >= 0)
          {
            *--outptr = alpha;
            *--outptr = *inptr++;
            *--outptr = *inptr++;
            *--outptr = *inptr++;
            inptr++;
            outptr += 8;
          }
        }
        break;
      }
      break;
  }
}

//------------------------------------------------------------------------------
void vtkWin32VideoSource::VideoFormatDialog()
{
  this->Initialize();
  if (!this->Initialized)
  {
    return;
  }

  // if (!this->Internal->CapDriverCaps.fHasDlgVideoFormat)
  //  {
  //  MessageBox(this->Internal->ParentWnd,"The video device has no Format dialog.","",
  //             MB_OK | MB_ICONEXCLAMATION);
  //  return;
  //  }

  capGetStatus(this->Internal->CapWnd, &this->Internal->CapStatus, sizeof(CAPSTATUS));
  if (this->Internal->CapStatus.fCapturingNow)
  {
    MessageBox(this->Internal->ParentWnd, "Can't alter video format while grabbing.", "",
      MB_OK | MB_ICONEXCLAMATION);
    return;
  }

  int success = capDlgVideoFormat(this->Internal->CapWnd);
  if (success)
  {
    this->FrameBufferMutex.lock();
    this->DoVFWFormatCheck();
    this->FrameBufferMutex.unlock();
  }
}

//------------------------------------------------------------------------------
void vtkWin32VideoSource::VideoSourceDialog()
{
  this->Initialize();
  if (!this->Initialized)
  {
    return;
  }

  // if (!this->Internal->CapDriverCaps.fHasDlgVideoSource)
  //  {
  //  MessageBox(this->Internal->ParentWnd,"The video device has no Source dialog.","",
  //             MB_OK | MB_ICONEXCLAMATION);
  //  return;
  //  }

  capGetStatus(this->Internal->CapWnd, &this->Internal->CapStatus, sizeof(CAPSTATUS));
  if (this->Internal->CapStatus.fCapturingNow)
  {
    MessageBox(this->Internal->ParentWnd, "Can't alter video source while grabbing.", "",
      MB_OK | MB_ICONEXCLAMATION);
    return;
  }

  int success = capDlgVideoSource(this->Internal->CapWnd);
  if (success)
  {
    this->FrameBufferMutex.lock();
    this->DoVFWFormatCheck();
    this->FrameBufferMutex.unlock();
  }
}

//------------------------------------------------------------------------------
// try for the specified frame size
void vtkWin32VideoSource::SetFrameSize(int x, int y, int z)
{
  if (x == this->FrameSize[0] && y == this->FrameSize[1] && z == this->FrameSize[2])
  {
    return;
  }

  if (x < 1 || y < 1 || z != 1)
  {
    vtkErrorMacro(<< "SetFrameSize: Illegal frame size");
    return;
  }

  this->FrameSize[0] = x;
  this->FrameSize[1] = y;
  this->FrameSize[2] = z;
  this->Modified();

  if (this->Initialized)
  {
    this->FrameBufferMutex.lock();
    this->UpdateFrameBuffer();
    this->DoVFWFormatSetup();
    this->FrameBufferMutex.unlock();
  }
}

//------------------------------------------------------------------------------
void vtkWin32VideoSource::SetFrameRate(float rate)
{
  if (rate == this->FrameRate)
  {
    return;
  }

  this->FrameRate = rate;
  this->Modified();

  if (this->Initialized)
  {
    capCaptureGetSetup(this->Internal->CapWnd, &this->Internal->CaptureParms, sizeof(CAPTUREPARMS));
    if (this->FrameRate > 0)
    {
      this->Internal->CaptureParms.dwRequestMicroSecPerFrame = int(1000000 / this->FrameRate);
    }
    else
    {
      this->Internal->CaptureParms.dwRequestMicroSecPerFrame = 0;
    }
    capCaptureSetSetup(this->Internal->CapWnd, &this->Internal->CaptureParms, sizeof(CAPTUREPARMS));
  }
}

//------------------------------------------------------------------------------
void vtkWin32VideoSource::SetOutputFormat(int format)
{
  if (format == this->OutputFormat)
  {
    return;
  }

  this->OutputFormat = format;

  // convert color format to number of scalar components
  int numComponents;

  switch (this->OutputFormat)
  {
    case VTK_RGBA:
      numComponents = 4;
      break;
    case VTK_RGB:
      numComponents = 3;
      break;
    case VTK_LUMINANCE:
      numComponents = 1;
      break;
    default:
      numComponents = 0;
      vtkErrorMacro(<< "SetOutputFormat: Unrecognized color format.");
      break;
  }
  this->NumberOfScalarComponents = numComponents;

  if (this->FrameBufferBitsPerPixel != numComponents * 8)
  {
    this->FrameBufferMutex.lock();
    this->FrameBufferBitsPerPixel = numComponents * 8;
    if (this->Initialized)
    {
      this->UpdateFrameBuffer();
      this->DoVFWFormatSetup();
    }
    this->FrameBufferMutex.unlock();
  }

  this->Modified();
}

//------------------------------------------------------------------------------
// check the current video format and set up the VTK video framebuffer to match
void vtkWin32VideoSource::DoVFWFormatCheck()
{
  // get the real video format
  int formatSize = capGetVideoFormatSize(this->Internal->CapWnd);
  if (formatSize > this->BitMapSize)
  {
    delete[] ((char*)this->Internal->BitMapPtr);
    this->Internal->BitMapPtr = (LPBITMAPINFO) new char[formatSize];
    this->BitMapSize = formatSize;
  }
  capGetVideoFormat(this->Internal->CapWnd, this->Internal->BitMapPtr, formatSize);

  int bpp = this->Internal->BitMapPtr->bmiHeader.biBitCount;
  int width = this->Internal->BitMapPtr->bmiHeader.biWidth;
  int height = this->FrameSize[1] = this->Internal->BitMapPtr->bmiHeader.biHeight;
  int compression = this->Internal->BitMapPtr->bmiHeader.biCompression;

  if (compression == VTK_BI_UYVY)
  {
    this->FlipFrames = 1;
  }
  else if (compression == BI_RGB)
  {
    this->FlipFrames = 0;
  }
  else
  {
    char fourcchex[16], fourcc[8];
    snprintf(fourcchex, sizeof(fourcchex), "0x%08x", compression);
    for (int i = 0; i < 4; i++)
    {
      fourcc[i] = (compression >> (8 * i)) & 0xff;
      if (!isprint(fourcc[i]))
      {
        fourcc[i] = '?';
      }
    }
    fourcc[4] = '\0';
    vtkWarningMacro(<< "DoVFWFormatCheck: video compression mode " << fourcchex << " \"" << fourcc
                    << "\": can't grab");
  }

  if (bpp != this->FrameBufferBitsPerPixel)
  {
    switch (bpp)
    {
      case 1:
      case 4:
      case 8:
        this->OutputFormat = VTK_LUMINANCE;
        this->NumberOfScalarComponents = 1;
        break;
      case 16:
        if (compression != VTK_BI_UYVY)
        {
          this->OutputFormat = VTK_RGB;
          this->NumberOfScalarComponents = 3;
        }
        break;
      case 24:
      case 32:
        if (this->OutputFormat != VTK_RGBA)
        {
          this->OutputFormat = VTK_RGB;
          this->NumberOfScalarComponents = 3;
        }
        break;
    }
  }

  if (bpp != this->FrameBufferBitsPerPixel || this->FrameSize[0] != width ||
    this->FrameSize[1] != height)
  {
    this->FrameBufferBitsPerPixel = bpp;
    this->FrameSize[0] = width;
    this->FrameSize[1] = height;
    this->Modified();
    this->UpdateFrameBuffer();
  }
}

//------------------------------------------------------------------------------
void vtkWin32VideoSource::DoVFWFormatSetup()
{
  static const int colorBits[3] = { 24, 32, 16 };
  static const int greyBits[3] = { 8, 4, 1 };
  int i, bytesPerRow, bitCount;

  // get the real video format
  int formatSize = capGetVideoFormatSize(this->Internal->CapWnd);
  if (formatSize > this->BitMapSize)
  {
    delete[] ((char*)this->Internal->BitMapPtr);
    this->Internal->BitMapPtr = (LPBITMAPINFO) new char[formatSize];
    this->BitMapSize = formatSize;
  }
  capGetVideoFormat(this->Internal->CapWnd, this->Internal->BitMapPtr, formatSize);

  // set the format of the captured frames
  this->Internal->BitMapPtr->bmiHeader.biWidth = this->FrameSize[0];
  this->Internal->BitMapPtr->bmiHeader.biHeight = this->FrameSize[1];
  this->Internal->BitMapPtr->bmiHeader.biCompression = BI_RGB;
  this->Internal->BitMapPtr->bmiHeader.biClrUsed = 0;
  this->Internal->BitMapPtr->bmiHeader.biClrImportant = 0;

  for (i = 0; i < 3; i++)
  { // try for a
    if (this->OutputFormat == VTK_RGBA || this->OutputFormat == VTK_RGB)
    {
      bitCount = colorBits[i];
    }
    else
    {
      bitCount = greyBits[i];
    }
    bytesPerRow = (this->FrameSize[0] * bitCount + 7) / 8;
    bytesPerRow += bytesPerRow % this->FrameBufferRowAlignment;
    this->Internal->BitMapPtr->bmiHeader.biBitCount = bitCount;
    this->Internal->BitMapPtr->bmiHeader.biSizeImage = bytesPerRow * this->FrameSize[1];
    if (capSetVideoFormat(
          this->Internal->CapWnd, this->Internal->BitMapPtr, sizeof(BITMAPINFOHEADER)))
    {
      break;
    }
  }
  if (i > 4)
  {
    vtkWarningMacro(<< "DoVFWFormatSetup: invalid video format for device"
                    << " (" << GetLastError() << ")");
  }
  this->DoVFWFormatCheck();
}
VTK_ABI_NAMESPACE_END
