// +-------------------------------- DISCLAIMER ---------------------------------+
// |                                                                             |
// | This program is provided to you free of charge "as such".                   |
// | You should not assume that this program is error-free or suitable for any   |
// | purpose whatsoever.                                                         |
// |                                                                             |
// | EURESYS does not give any representation, warranty or undertaking that this |
// | program is free of any defect or error or suitable for any purpose. EURESYS |
// | shall not be liable, in contract, in torts or otherwise, for any damages,   |
// | loss, costs, expenses or other claims for compensation, including those     |
// | asserted by third parties, arising out of or in connection with the use of  |
// | this program.                                                               |
// +-----------------------------------------------------------------------------+

#include "../Common/stdafx.h"
#include "C2DView.h"
#include <sstream>
#include <limits>

BEGIN_MESSAGE_MAP(C2DView, CStatic)
  ON_WM_PAINT()
  ON_WM_MOVE()
  ON_WM_RBUTTONDOWN()
  ON_WM_LBUTTONDOWN()
  ON_WM_LBUTTONUP()
  ON_WM_MOUSEWHEEL()
  ON_WM_MOUSEMOVE()
  ON_MESSAGE(WM_MOUSELEAVE, OnMouseLeave)
END_MESSAGE_MAP()

C2DView::C2DView() : m_ImageWidth(0), m_ImageHeight(0), m_ZoomFactor(1.f), m_OrgX(0), m_OrgY(0),
m_TrackLeave(false), m_Dragging(false)
{
  m_DragOrigin.SetPoint(0, 0);
  m_DragOffset.SetPoint(0, 0);
}

C2DView::~C2DView()
{
}

afx_msg void C2DView::OnRButtonDown(UINT nFlags, CPoint point)
{
  OnLButtonDown(nFlags, point);
}

afx_msg void C2DView::OnLButtonDown(UINT nFlags, CPoint point)
{
  // By default, assume that we are starting to drag the image
  m_Dragging = true;

  // Store drag origin
  m_DragOrigin = point;

  ::SetFocus(m_hWnd);
}

afx_msg void C2DView::OnLButtonUp(UINT nFlags, CPoint point)
{
  // If we were dragging the image, apply the drag offset to the image panning
  if (m_Dragging)
  {
    m_OrgX -= m_DragOffset.x;
    m_OrgY -= m_DragOffset.y;

    m_DragOffset.x = 0;
    m_DragOffset.y = 0;
  }

  // We are no longer dragging anything
  m_Dragging = false;

  // Call for repaint
  Invalidate();
}

afx_msg BOOL C2DView::OnMouseWheel(UINT nFlags, short zDelta, CPoint point)
{
  // Store the old ZoomFactor
  float oldZoomFactor = m_ZoomFactor;

  // Zoom in/out but keep the mouse cursor at the same image position
  point.x -= m_ViewRect.left;
  point.y -= m_ViewRect.top;
  int px = int(m_OrgX + point.x / m_ZoomFactor);
  int py = int(m_OrgY + point.y / m_ZoomFactor);

  // Adjust the zoom factor
  if (zDelta > 0)
    m_ZoomFactor *= 2.f;
  else if (zDelta < 0)
    m_ZoomFactor *= 0.5f;

  // Make sure that we do not zoom out so far that the image is smaller than 1/2 of the viewRect, although a zoomFactor of 1 should always be allowed
  if (m_ZoomFactor < 1.f)
  {
    float zoomed_Width = m_ImageWidth * m_ZoomFactor;
    float zoomed_Height = m_ImageHeight * m_ZoomFactor;
    if (zoomed_Width < 0.5 * m_ViewRect.Width() && zoomed_Height < 0.5f * m_ViewRect.Height())
      m_ZoomFactor = oldZoomFactor;
  }

  // Make sure that we do not zoom in so far that we see less than 10 pixels in the viewRect, although a zoomLevel of 0 should always be allowed
  if (m_ZoomFactor > 1.f)
  {
    float zoomed_Width = m_ViewRect.Width() / m_ZoomFactor;
    float zoomed_Height = m_ViewRect.Height() / m_ZoomFactor;

    if (zoomed_Width < 10 || zoomed_Height < 10)
      m_ZoomFactor = oldZoomFactor;
  }

  m_OrgX += int(point.x / oldZoomFactor - point.x / m_ZoomFactor);
  m_OrgY += int(point.y / oldZoomFactor - point.y / m_ZoomFactor);
  if (m_OrgX < 0 || m_OrgX + m_ViewRect.Width() / m_ZoomFactor >= m_ImageWidth)
    m_OrgX = 0;
  if (m_OrgY < 0 || m_OrgY + m_ViewRect.Height() / m_ZoomFactor >= m_ImageHeight)
    m_OrgY = 0;

  // Call for repaint
  Invalidate();

  return 0;
}

afx_msg void C2DView::OnMouseMove(UINT nFlags, CPoint point)
{
  // Make sure that we register the moment when the mouse leaves the window
  if (!m_TrackLeave)
  {
    TRACKMOUSEEVENT tme;
    tme.cbSize = sizeof(tme);
    tme.hwndTrack = m_hWnd;
    tme.dwFlags = TME_LEAVE;
    _TrackMouseEvent(&tme);
    m_TrackLeave = true;
  }

  // If drag mode is active, update m_DragOffset
  if (m_Dragging)
  {
    // If dragging the image, check that it will stay inside the view area
    int view_width = m_ViewRect.right - m_ViewRect.left;
    int view_height = m_ViewRect.bottom - m_ViewRect.top;

    CPoint offset = point - m_DragOrigin;
    offset.x = int(offset.x / m_ZoomFactor);
    offset.y = int(offset.y / m_ZoomFactor);

    int x = m_OrgX - offset.x;
    if (x >= 0 && x + view_width / m_ZoomFactor < m_ImageWidth)
    {
      m_DragOffset.x = offset.x;
    }

    int y = m_OrgY - offset.y;
    if (y >= 0 && y + view_height / m_ZoomFactor < m_ImageHeight)
    {
      m_DragOffset.y = offset.y;
    }

    Invalidate();
  }
}

afx_msg LPARAM C2DView::OnMouseLeave(WPARAM wParam, LPARAM lParam)
{
  // Act as if the left button was lifted
  OnLButtonUp(0, CPoint(0, 0));

  // We no longer have to track the leave event, it has already happened
  m_TrackLeave = false;

  return 0;
}

afx_msg void C2DView::OnMove(int x, int y)
{
  GetWindowRect(&m_ViewRect);
}

afx_msg void C2DView::OnPaint()
{
  if (IsIconic())
    return;

  GetWindowRect(&m_ViewRect);

  PAINTSTRUCT ps;
  CStatic::BeginPaint(&ps);

  // Refresh 2D View
  CDC* dc = GetDC();
  HDC view_hdc = dc->GetSafeHdc();

  try
  {
    // Get view size
    int view_width = m_ViewRect.right - m_ViewRect.left;
    int view_height = m_ViewRect.bottom - m_ViewRect.top;

    // Get the panning
    float panX = float(-m_OrgX + m_DragOffset.x);
    float panY = float(-m_OrgY + m_DragOffset.y);

    // Render in a color image and blit to view
    EImageC24 view_image(view_width, view_height);
    EasyImage::Copy(EC24(220, 220, 220), &view_image);
    EWindowsDrawAdapter drawAdapter(&view_image);

    m_Image.Draw(&drawAdapter, m_ZoomFactor, m_ZoomFactor, panX, panY);

    // Display 3D objects
    ERGBColor color = ERGBColor(255, 255, 0);
    for (size_t i = 0; i < m_ObjList.size(); i++)
      m_ObjList[i].Draw(&drawAdapter, Easy3D::E3DObjectFeature_ERectangleRegion, color, m_ZoomFactor, m_ZoomFactor, panX, panY);

    BitBlt(view_hdc, 0, 0, view_width, view_height, drawAdapter.GetHDC(), 0, 0, SRCCOPY);
    drawAdapter.Close();
  }
  catch (EException exc)
  {
    if (exc.GetError() != EError_LicenseMissing)
    {
      ::MessageBox(NULL, CString(exc.what()), _T("Error"), MB_OK | MB_DEFAULT_DESKTOP_ONLY);
    }
  }

  ReleaseDC(dc);

  CStatic::EndPaint(&ps);
}

void C2DView::SetImage(const EImageBW8& image)
{
  m_Image = image;

  if (m_ImageWidth != image.GetWidth() || m_ImageHeight != image.GetHeight())
  {
    m_ImageWidth = image.GetWidth();
    m_ImageHeight = image.GetHeight();

    // Reset zoom and origin
    int view_width = m_ViewRect.right - m_ViewRect.left;
    m_ZoomFactor = (float)view_width / m_ImageWidth;
    m_OrgX = m_OrgY = 0;
  }

  Invalidate();
}

void C2DView::SetDepthMap(Easy3D::EDepthMap* map)
{
  switch (map->GetType())
  {
  case EImageType_Depth8:
    // Direct copy
    m_Image = dynamic_cast<Easy3D::EDepthMap8*>(map)->AsEImage();
    break;

  case EImageType_Depth16:
  {
    // Scale to fit 8 bits dynamic
    EImageBW16* im16 = &(dynamic_cast<Easy3D::EDepthMap16*>(map)->AsEImage());

    EImageStatisticsBW16 stats;
    stats.SetImage(im16);

    int shift = (int)(log2(stats.GetPixelMax().Value)) - 7;
    if (shift < 0)
      shift = 0;

    m_Image.SetSize(map->GetWidth(), map->GetHeight());
    EasyImage::Convert(im16, &m_Image, shift);
  }
  break;
  }

  if (m_ImageWidth != map->GetWidth() || m_ImageHeight != map->GetHeight())
  {
    m_ImageWidth = map->GetWidth();
    m_ImageHeight = map->GetHeight();

    // Reset zoom and origin
    int view_width = m_ViewRect.right - m_ViewRect.left;
    m_ZoomFactor = (float)view_width / m_ImageWidth;
    m_OrgX = m_OrgY = 0;
  }

  Invalidate();
}

void C2DView::SetZMap(Easy3D::EZMap* map)
{
  switch (map->GetType())
  {
  case EImageType_Depth8:
    m_Image = dynamic_cast<Easy3D::EZMap8*>(map)->AsEImage();
    break;

  case EImageType_Depth16:
  {
    // Scale to fit 8 bits dynamic
    EImageBW16* im16 = &(dynamic_cast<Easy3D::EZMap16*>(map)->AsEImage());

    EImageStatisticsBW16 stats;
    stats.SetImage(im16);

    int shift = (int)(log2(stats.GetPixelMax().Value)) - 7;
    if (shift < 0)
      shift = 0;

    m_Image.SetSize(map->GetWidth(), map->GetHeight());
    EasyImage::Convert(im16, &m_Image, shift);
  }
  break;

  case EImageType_Depth32f:
    m_Image.SetSize(map->GetWidth(), map->GetHeight());
    Easy3D::EZMap16 zmap16;
    Easy3D::EConverter::Convert(*dynamic_cast<Easy3D::EZMap32f*>(map), zmap16);
    EasyImage::Convert(&(zmap16.AsEImage()), &m_Image);
    break;
  }

  if (m_ImageWidth != map->GetWidth() || m_ImageHeight != map->GetHeight())
  {
    m_ImageWidth = map->GetWidth();
    m_ImageHeight = map->GetHeight();

    // Reset zoom and origin
    int view_width = m_ViewRect.right - m_ViewRect.left;
    m_ZoomFactor = (float)view_width / m_ImageWidth;
    m_OrgX = m_OrgY = 0;
  }

  // Clear object list
  m_ObjList.clear();

  Invalidate();
}

void C2DView::SetObjectList(const std::vector<Easy3D::E3DObject>& object_list)
{
  m_ObjList = object_list;

  Invalidate();
}
