/*=========================================================================

  Program:   Visualization Toolkit
  Module:    vtkMFCWindow.cpp

  Copyright (c) Ken Martin, Will Schroeder, Bill Lorensen
  All rights reserved.
  See Copyright.txt or http://www.kitware.com/Copyright.htm for details.

     This software is distributed WITHOUT ANY WARRANTY; without even
     the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
     PURPOSE.  See the above copyright notice for more information.

=========================================================================*/

// 0x0501 means target Windows XP or later
#ifndef WINVER
#define WINVER 0x0501
#endif

// Define _WIN32_WINNT and _WIN32_IE to avoid the following error with Visual
// Studio 2008 SP1:
// "C:\Program Files\Microsoft SDKs\Windows\v6.0A\include\sdkddkver.h(217) :
// fatal error C1189: #error :  _WIN32_WINNT settings conflicts with _WIN32_IE
// setting"

#ifndef _WIN32_WINNT
#define _WIN32_WINNT 0x0501 // 0x0501 means target Windows XP or later
#endif
#ifndef _WIN32_IE
#define _WIN32_IE 0x0601 //=_WIN32_IE_IE60SP1
#endif

#include "vtkMFCWindow.h"

#include "vtkWin32OpenGLRenderWindow.h"
#include "vtkWin32RenderWindowInteractor.h"

#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif

BEGIN_MESSAGE_MAP(vtkMFCWindow, CWnd)
ON_WM_SIZE()
ON_WM_PAINT()
ON_WM_DESTROY()
ON_WM_ERASEBKGND()

ON_WM_LBUTTONDBLCLK()
ON_WM_LBUTTONDOWN()
ON_WM_MBUTTONDOWN()
ON_WM_RBUTTONDOWN()
ON_WM_LBUTTONUP()
ON_WM_MBUTTONUP()
ON_WM_RBUTTONUP()
ON_WM_MOUSEMOVE()
ON_WM_MOUSEWHEEL()
ON_WM_CHAR()
ON_WM_KEYUP()
ON_WM_KEYDOWN()
ON_WM_TIMER()

END_MESSAGE_MAP()

#ifdef _DEBUG
void vtkMFCWindow::AssertValid() const
{
  CWnd::AssertValid();
}

void vtkMFCWindow::Dump(CDumpContext& dc) const
{
  CWnd::Dump(dc);
}
#endif //_DEBUG

vtkMFCWindow::vtkMFCWindow(CWnd* pcWnd)
{
  this->pvtkWin32OpenGLRW = nullptr;

  // create self as a child of passed in parent
  DWORD style = WS_VISIBLE | WS_CLIPSIBLINGS;
  if (pcWnd)
    style |= WS_CHILD;
  BOOL bCreated =
    CWnd::Create(nullptr, _T("VTK-MFC Window"), style, CRect(0, 0, 1, 1), pcWnd, (UINT)IDC_STATIC);

  SUCCEEDED(bCreated);

  // create a default vtk window
  vtkWin32OpenGLRenderWindow* win = vtkWin32OpenGLRenderWindow::New();
  this->SetRenderWindow(win);
  win->Delete();
}

vtkMFCWindow::~vtkMFCWindow()
{
  this->SetRenderWindow(nullptr);
}

void vtkMFCWindow::OnDestroy()
{
  if (this->pvtkWin32OpenGLRW && this->pvtkWin32OpenGLRW->GetMapped())
    this->pvtkWin32OpenGLRW->Finalize();

  CWnd::OnDestroy();
}

void vtkMFCWindow::SetRenderWindow(vtkWin32OpenGLRenderWindow* win)
{

  if (this->pvtkWin32OpenGLRW)
  {
    if (this->pvtkWin32OpenGLRW->GetMapped())
      this->pvtkWin32OpenGLRW->Finalize();
    this->pvtkWin32OpenGLRW->UnRegister(nullptr);
  }

  this->pvtkWin32OpenGLRW = win;

  if (this->pvtkWin32OpenGLRW)
  {
    this->pvtkWin32OpenGLRW->Register(nullptr);

    vtkWin32RenderWindowInteractor* iren = vtkWin32RenderWindowInteractor::New();
    iren->SetInstallMessageProc(0);

    // setup the parent window
    this->pvtkWin32OpenGLRW->SetWindowId(this->GetSafeHwnd());
    this->pvtkWin32OpenGLRW->SetParentId(::GetParent(this->GetSafeHwnd()));
    iren->SetRenderWindow(this->pvtkWin32OpenGLRW);

    iren->Initialize();

    // update size
    CRect cRect = CRect(0, 0, 1, 1);
    if (this->GetParent())
      this->GetParent()->GetClientRect(&cRect);
    if (iren->GetInitialized())
      iren->UpdateSize(cRect.Width(), cRect.Height());

    // release our hold on interactor
    iren->Delete();
  }
}

vtkWin32OpenGLRenderWindow* vtkMFCWindow::GetRenderWindow()
{
  return this->pvtkWin32OpenGLRW;
}

vtkRenderWindowInteractor* vtkMFCWindow::GetInteractor()
{
  if (!this->pvtkWin32OpenGLRW)
  {
    return nullptr;
  }
  return this->pvtkWin32OpenGLRW->GetInteractor();
}

void vtkMFCWindow::OnPaint()
{
  CPaintDC dc(this);
  if (this->GetInteractor() && this->GetInteractor()->GetInitialized())
  {
    this->GetInteractor()->Render();
  }
}

void vtkMFCWindow::DrawDC(CDC* pDC)
{
  // Obtain the size of the printer page in pixels.
  int cxPage = pDC->GetDeviceCaps(HORZRES);
  int cyPage = pDC->GetDeviceCaps(VERTRES);

  // Get the size of the window in pixels.
  const int* size = this->pvtkWin32OpenGLRW->GetSize();
  int cxWindow = size[0];
  int cyWindow = size[1];
  float fx = float(cxPage) / float(cxWindow);
  float fy = float(cyPage) / float(cyWindow);
  float scale = min(fx, fy);
  int x = int(scale * float(cxWindow));
  int y = int(scale * float(cyWindow));

  this->pvtkWin32OpenGLRW->SetUseOffScreenBuffers(true);
  this->pvtkWin32OpenGLRW->Render();

  unsigned char* pixels =
    this->pvtkWin32OpenGLRW->GetPixelData(0, 0, size[0] - 1, size[1] - 1, 0, 0);

  // now copy he result to the HDC
  int dataWidth = ((cxWindow * 3 + 3) / 4) * 4;

  BITMAPINFO MemoryDataHeader;
  MemoryDataHeader.bmiHeader.biSize = 40;
  MemoryDataHeader.bmiHeader.biWidth = cxWindow;
  MemoryDataHeader.bmiHeader.biHeight = cyWindow;
  MemoryDataHeader.bmiHeader.biPlanes = 1;
  MemoryDataHeader.bmiHeader.biBitCount = 24;
  MemoryDataHeader.bmiHeader.biCompression = BI_RGB;
  MemoryDataHeader.bmiHeader.biClrUsed = 0;
  MemoryDataHeader.bmiHeader.biClrImportant = 0;
  MemoryDataHeader.bmiHeader.biSizeImage = dataWidth * cyWindow;
  MemoryDataHeader.bmiHeader.biXPelsPerMeter = 10000;
  MemoryDataHeader.bmiHeader.biYPelsPerMeter = 10000;

  unsigned char* MemoryData; // the data in the DIBSection
  HDC MemoryHdc = (HDC)CreateCompatibleDC(pDC->GetSafeHdc());
  HBITMAP dib = CreateDIBSection(
    MemoryHdc, &MemoryDataHeader, DIB_RGB_COLORS, (void**)(&(MemoryData)), nullptr, 0);

  // copy the pixels over
  for (int i = 0; i < cyWindow; i++)
  {
    for (int j = 0; j < cxWindow; j++)
    {
      MemoryData[i * dataWidth + j * 3] = pixels[i * cxWindow * 3 + j * 3 + 2];
      MemoryData[i * dataWidth + j * 3 + 1] = pixels[i * cxWindow * 3 + j * 3 + 1];
      MemoryData[i * dataWidth + j * 3 + 2] = pixels[i * cxWindow * 3 + j * 3];
    }
  }

  // Put the bitmap into the device context
  SelectObject(MemoryHdc, dib);
  StretchBlt(pDC->GetSafeHdc(), 0, 0, x, y, MemoryHdc, 0, 0, cxWindow, cyWindow, SRCCOPY);

  this->pvtkWin32OpenGLRW->SetUseOffScreenBuffers(false);
  delete[] pixels;
}

void vtkMFCWindow::OnSize(UINT nType, int cx, int cy)
{
  CWnd::OnSize(nType, cx, cy);
  if (this->GetInteractor() && this->GetInteractor()->GetInitialized())
    this->GetInteractor()->UpdateSize(cx, cy);
}

BOOL vtkMFCWindow::OnEraseBkgnd(CDC*)
{
  return TRUE;
}

void vtkMFCWindow::OnLButtonDblClk(UINT nFlags, CPoint point)
{
  static_cast<vtkWin32RenderWindowInteractor*>(this->GetInteractor())
    ->OnLButtonDown(this->GetSafeHwnd(), nFlags, point.x, point.y, 1);
}

void vtkMFCWindow::OnLButtonDown(UINT nFlags, CPoint point)
{
  this->SetFocus();
  static_cast<vtkWin32RenderWindowInteractor*>(this->GetInteractor())
    ->OnLButtonDown(this->GetSafeHwnd(), nFlags, point.x, point.y, 0);
}

void vtkMFCWindow::OnMButtonDown(UINT nFlags, CPoint point)
{
  this->SetFocus();
  static_cast<vtkWin32RenderWindowInteractor*>(this->GetInteractor())
    ->OnMButtonDown(this->GetSafeHwnd(), nFlags, point.x, point.y, 0);
}

void vtkMFCWindow::OnRButtonDown(UINT nFlags, CPoint point)
{
  this->SetFocus();
  static_cast<vtkWin32RenderWindowInteractor*>(this->GetInteractor())
    ->OnRButtonDown(this->GetSafeHwnd(), nFlags, point.x, point.y, 0);
}

void vtkMFCWindow::OnLButtonUp(UINT nFlags, CPoint point)
{
  static_cast<vtkWin32RenderWindowInteractor*>(this->GetInteractor())
    ->OnLButtonUp(this->GetSafeHwnd(), nFlags, point.x, point.y);
}

void vtkMFCWindow::OnMButtonUp(UINT nFlags, CPoint point)
{
  static_cast<vtkWin32RenderWindowInteractor*>(this->GetInteractor())
    ->OnMButtonUp(this->GetSafeHwnd(), nFlags, point.x, point.y);
}

void vtkMFCWindow::OnRButtonUp(UINT nFlags, CPoint point)
{
  static_cast<vtkWin32RenderWindowInteractor*>(this->GetInteractor())
    ->OnRButtonUp(this->GetSafeHwnd(), nFlags, point.x, point.y);
}

void vtkMFCWindow::OnMouseMove(UINT nFlags, CPoint point)
{
  static_cast<vtkWin32RenderWindowInteractor*>(this->GetInteractor())
    ->OnMouseMove(this->GetSafeHwnd(), nFlags, point.x, point.y);
}

BOOL vtkMFCWindow::OnMouseWheel(UINT nFlags, short zDelta, CPoint point)
{
  // Point is in screen space, translate to the client space.
  ScreenToClient(&point);
  if (zDelta > 0)
    static_cast<vtkWin32RenderWindowInteractor*>(this->GetInteractor())
      ->OnMouseWheelForward(this->GetSafeHwnd(), nFlags, point.x, point.y);
  else
    static_cast<vtkWin32RenderWindowInteractor*>(this->GetInteractor())
      ->OnMouseWheelBackward(this->GetSafeHwnd(), nFlags, point.x, point.y);
  return TRUE;
}

void vtkMFCWindow::OnChar(UINT nChar, UINT nRepCnt, UINT nFlags)
{
  static_cast<vtkWin32RenderWindowInteractor*>(this->GetInteractor())
    ->OnChar(this->GetSafeHwnd(), nChar, nRepCnt, nFlags);
}

void vtkMFCWindow::OnKeyUp(UINT nChar, UINT nRepCnt, UINT nFlags)
{
  static_cast<vtkWin32RenderWindowInteractor*>(this->GetInteractor())
    ->OnKeyUp(this->GetSafeHwnd(), nChar, nRepCnt, nFlags);
}

void vtkMFCWindow::OnKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags)
{
  static_cast<vtkWin32RenderWindowInteractor*>(this->GetInteractor())
    ->OnKeyDown(this->GetSafeHwnd(), nChar, nRepCnt, nFlags);
}

void vtkMFCWindow::OnTimer(UINT_PTR nIDEvent)
{
  static_cast<vtkWin32RenderWindowInteractor*>(this->GetInteractor())
    ->OnTimer(this->GetSafeHwnd(), (UINT)nIDEvent);
}
