/* +------------------------------------------------------------------------+
   |                     Mobile Robot Programming Toolkit (MRPT)            |
   |                          https://www.mrpt.org/                         |
   |                                                                        |
   | Copyright (c) 2005-2024, Individual contributors, see AUTHORS file     |
   | See: https://www.mrpt.org/Authors - All rights reserved.               |
   | Released under BSD License. See: https://www.mrpt.org/License          |
   +------------------------------------------------------------------------+ */

#include "gui-precomp.h"  // Precompiled headers
//
#include <mrpt/core/lock_helper.h>
#include <mrpt/core/round.h>
#include <mrpt/gui/CDisplayWindow.h>
#include <mrpt/gui/WxSubsystem.h>
#include <mrpt/gui/WxUtils.h>
#include <mrpt/img/CImage.h>
#include <mrpt/system/os.h>

using namespace mrpt;
using namespace mrpt::math;
using namespace mrpt::gui;
using namespace mrpt::system;
using namespace std;
using namespace mrpt::img;

#if MRPT_HAS_WXWIDGETS

BEGIN_EVENT_TABLE(CWindowDialog, wxFrame)

END_EVENT_TABLE()

const long CWindowDialog::ID_IMAGE_BITMAP = wxNewId();
const long ID_MENUITEM1 = wxNewId();
const long ID_MENUITEM2 = wxNewId();
const long ID_MENUITEM3 = wxNewId();

CWindowDialog::wxMRPTImageControl::wxMRPTImageControl(
    wxWindow* parent, wxWindowID winID, int x, int y, int width, int height) :
    m_img(nullptr)
{
  this->Create(parent, winID, wxPoint(x, y), wxSize(width, height));

  Bind(wxEVT_PAINT, &CWindowDialog::wxMRPTImageControl::OnPaint, this);
  Bind(wxEVT_MOTION, &CWindowDialog::wxMRPTImageControl::OnMouseMove, this);
  Bind(wxEVT_LEFT_DOWN, &CWindowDialog::wxMRPTImageControl::OnMouseClick, this);
  Bind(wxEVT_CHAR, &CWindowDialog::wxMRPTImageControl::OnChar, this, wxID_ANY);
  Bind(wxEVT_CHAR, &CWindowDialog::wxMRPTImageControl::OnChar, this);
}

CWindowDialog::wxMRPTImageControl::~wxMRPTImageControl()
{
  std::lock_guard<std::mutex> lock(m_img_cs);
  if (m_img) m_img.reset();
}

void CWindowDialog::wxMRPTImageControl::OnMouseMove(wxMouseEvent& ev)
{
  // std::lock_guard<std::mutex>	lock( m_mouse_cs);
  m_last_mouse_point = ev.GetPosition();
}

void CWindowDialog::wxMRPTImageControl::OnMouseClick(wxMouseEvent& ev)
{
  // std::lock_guard<std::mutex>	lock( m_mouse_cs);
  m_last_mouse_click = ev.GetPosition();
}

void CWindowDialog::wxMRPTImageControl::OnChar(wxKeyEvent& ev) {}
void CWindowDialog::wxMRPTImageControl::AssignImage(wxBitmap* img)
{
  std::lock_guard<std::mutex> lock(m_img_cs);
  m_img.reset(img);
}

void CWindowDialog::wxMRPTImageControl::OnPaint(wxPaintEvent& ev)
{
  wxPaintDC dc(this);

  std::lock_guard<std::mutex> lock(m_img_cs);
  if (!m_img)
  {
    // Erase background:
    return;
  }

  dc.DrawBitmap(*m_img, 0, 0);
}

void CWindowDialog::wxMRPTImageControl::GetBitmap(wxBitmap& bmp)
{
  std::lock_guard<std::mutex> lock(m_img_cs);
  if (!m_img) return;
  bmp = *m_img;
}

CWindowDialog::CWindowDialog(
    CDisplayWindow* win2D,
    WxSubsystem::CWXMainFrame* parent,
    wxWindowID id,
    const std::string& caption,
    wxSize initialSize) :
    m_win2D(win2D), m_mainFrame(parent)
{
  Create(
      parent, id, caption.c_str(), wxDefaultPosition, initialSize, wxDEFAULT_FRAME_STYLE, _T("id"));
  SetClientSize(initialSize);

  wxIcon FrameIcon;
  FrameIcon.CopyFromBitmap(mrpt::gui::WxSubsystem::getMRPTDefaultIcon());
  SetIcon(FrameIcon);

  // Create the image object:
  m_image = new CWindowDialog::wxMRPTImageControl(this, ID_IMAGE_BITMAP, 0, 0, 10, 10);

  // wxCLIP_CHILDREN seems to avoid flicker
  SetWindowStyle(GetWindowStyle() | wxCLIP_CHILDREN);

  // Menu:
  auto* MenuBar1 = new wxMenuBar();

  auto* Menu1 = new wxMenu();
  wxMenuItem* MenuItem3 =
      new wxMenuItem(Menu1, ID_MENUITEM3, _("Save to file..."), _(""), wxITEM_NORMAL);
  Menu1->Append(MenuItem3);
  wxMenuItem* MenuItem1 = new wxMenuItem(Menu1, ID_MENUITEM1, _("Close"), _(""), wxITEM_NORMAL);
  Menu1->Append(MenuItem1);
  MenuBar1->Append(Menu1, _("&File"));

  auto* Menu2 = new wxMenu();
  wxMenuItem* MenuItem2 = new wxMenuItem(Menu2, ID_MENUITEM2, _("About..."), _(""), wxITEM_NORMAL);
  Menu2->Append(MenuItem2);
  MenuBar1->Append(Menu2, _("&Help"));

  SetMenuBar(MenuBar1);

  // Events:
  Bind(wxEVT_CLOSE_WINDOW, &CWindowDialog::OnClose, this, wxID_ANY);
  Bind(wxEVT_MENU, &CWindowDialog::OnMenuClose, this, ID_MENUITEM1);
  Bind(wxEVT_MENU, &CWindowDialog::OnMenuAbout, this, ID_MENUITEM2);
  Bind(wxEVT_MENU, &CWindowDialog::OnMenuSave, this, ID_MENUITEM3);

  Bind(wxEVT_KEY_DOWN, &CWindowDialog::OnChar, this, wxID_ANY);
  Bind(wxEVT_CHAR, &CWindowDialog::OnChar, this, wxID_ANY);

  m_image->Bind(wxEVT_KEY_DOWN, &CWindowDialog::OnChar, this);
  m_image->Bind(wxEVT_SIZE, &CWindowDialog::OnResize, this);

  m_image->Bind(wxEVT_LEFT_DOWN, &CWindowDialog::OnMouseDown, this);
  m_image->Bind(wxEVT_RIGHT_DOWN, &CWindowDialog::OnMouseDown, this);
  m_image->Bind(wxEVT_MOTION, &CWindowDialog::OnMouseMove, this);

  // Increment number of windows:
  // int winCount =
  WxSubsystem::CWXMainFrame::notifyWindowCreation();

  // this->Iconize(false);
}

// Destructor
CWindowDialog::~CWindowDialog() = default;
// OnClose event:
void CWindowDialog::OnClose(wxCloseEvent& event)
{
  // Send the event:
  bool allow_close = true;
  try
  {
    mrptEventWindowClosed ev(m_win2D, true /* allow close */);
    m_win2D->publishEvent(ev);
    allow_close = ev.allow_close;
  }
  catch (...)
  {
  }
  if (!allow_close) return;  // Don't process this close event.

  // Set the m_hwnd=nullptr in our parent object.
  m_win2D->notifyChildWindowDestruction();

  // Decrement number of windows:
  WxSubsystem::CWXMainFrame::notifyWindowDestruction();

  m_win2D->m_windowDestroyed.set_value();

  event.Skip();  // keep processing by parent classes.
}

void CWindowDialog::OnKeyDown(wxKeyEvent& event)
{
  event.Skip();  // So OnChar event is produced.
}

void CWindowDialog::OnChar(wxKeyEvent& event)
{
  if (m_win2D)
  {
    const int code = event.GetKeyCode();
    const mrptKeyModifier mod = mrpt::gui::keyEventToMrptKeyModifier(event);

    auto lck = mrpt::lockHelper(m_win2D->m_mtx);
    m_win2D->m_keyPushedCode = code;
    m_win2D->m_keyPushedModifier = mod;
    m_win2D->m_keyPushed = true;

    // Send the event:
    try
    {
      m_win2D->publishEvent(mrptEventWindowChar(m_win2D, code, mod));
    }
    catch (...)
    {
    }
  }
  event.Skip();
}

void CWindowDialog::OnResize(wxSizeEvent& event)
{
  // Send the event:
  if (m_win2D && m_win2D->hasSubscribers())
  {
    try
    {
      m_win2D->publishEvent(
          mrptEventWindowResize(m_win2D, event.GetSize().GetWidth(), event.GetSize().GetHeight()));
    }
    catch (...)
    {
    }
  }
  event.Skip();  // so it's processed by the wx system!
}

void CWindowDialog::OnMouseDown(wxMouseEvent& event)
{
  // Send the event:
  if (m_win2D && m_win2D->hasSubscribers())
  {
    try
    {
      m_win2D->publishEvent(mrptEventMouseDown(
          m_win2D, TPixelCoord(event.GetX(), event.GetY()), event.LeftDown(), event.RightDown()));
    }
    catch (...)
    {
    }
  }
  event.Skip();  // so it's processed by the wx system!
}

void CWindowDialog::OnMouseMove(wxMouseEvent& event)
{
  // Send the event:
  if (m_win2D && m_win2D->hasSubscribers())
  {
    try
    {
      m_win2D->publishEvent(mrptEventMouseMove(
          m_win2D, TPixelCoord(event.GetX(), event.GetY()), event.LeftDown(), event.RightDown()));
    }
    catch (...)
    {
    }
  }
  event.Skip();  // so it's processed by the wx system!
}

// Menu: Close
void CWindowDialog::OnMenuClose(wxCommandEvent& event) { Close(); }
// Menu: About
void CWindowDialog::OnMenuAbout(wxCommandEvent& event)
{
  ::wxMessageBox(_("Image viewer\n Class gui::CDisplayWindow\n MRPT C++ library"), _("About..."));
}

// Menu: Save to file
void CWindowDialog::OnMenuSave(wxCommandEvent& event)
{
  wxFileDialog dialog(
      this, wxT("Save image as..."), wxT("."), wxT("image.png"),
      wxT("PNG image files (*.png)|*.png"), wxFD_SAVE | wxFD_OVERWRITE_PROMPT);

  if (wxID_OK == dialog.ShowModal())
  {
    try
    {
      wxBitmap bmp;
      m_image->GetBitmap(bmp);
      bmp.SaveFile(dialog.GetPath(), wxBITMAP_TYPE_PNG);
    }
    catch (...)
    {
    }
  }
}

#endif

CDisplayWindow::Ptr CDisplayWindow::Create(
    const std::string& windowCaption, unsigned int initWidth, unsigned int initHeight)
{
  return std::make_shared<CDisplayWindow>(windowCaption, initWidth, initHeight);
}
/*---------------------------------------------------------------
          Constructor
 ---------------------------------------------------------------*/
CDisplayWindow::CDisplayWindow(
    const std::string& windowCaption, unsigned int initWidth, unsigned int initHeight) :
    CBaseGUIWindow(static_cast<void*>(this), 200, 299, windowCaption)

{
  CBaseGUIWindow::createWxWindow(initWidth, initHeight);
}

/*---------------------------------------------------------------
          Destructor
 ---------------------------------------------------------------*/
CDisplayWindow::~CDisplayWindow() { CBaseGUIWindow::destroyWxWindow(); }
/** Set cursor style to default (cursorIsCross=false) or to a cross
 * (cursorIsCross=true) */
void CDisplayWindow::setCursorCross([[maybe_unused]] bool cursorIsCross)
{
#if MRPT_HAS_WXWIDGETS && MRPT_HAS_OPENGL_GLUT
  const auto* win = (const CWindowDialog*)m_hwnd.get();
  if (!win) return;
  win->m_image->SetCursor(*(cursorIsCross ? wxCROSS_CURSOR : wxSTANDARD_CURSOR));
#endif
}

/*---------------------------------------------------------------
          getLastMousePosition
 ---------------------------------------------------------------*/
bool CDisplayWindow::getLastMousePosition([[maybe_unused]] int& x, [[maybe_unused]] int& y) const
{
#if MRPT_HAS_WXWIDGETS && MRPT_HAS_OPENGL_GLUT
  const auto* win = (const CWindowDialog*)m_hwnd.get();
  if (!win) return false;
  x = win->m_image->m_last_mouse_point.x;
  y = win->m_image->m_last_mouse_point.y;
  return true;
#else
  return false;
#endif
}

/*---------------------------------------------------------------
          showImage
 ---------------------------------------------------------------*/
void CDisplayWindow::showImage([[maybe_unused]] const CImage& img)
{
#if MRPT_HAS_WXWIDGETS
  MRPT_START

  // Send message of new image:
  wxImage* newImg = mrpt::gui::MRPTImage2wxImage(img);

  // Send a request to destroy this object:
  auto* REQ = new WxSubsystem::TRequestToWxMainThread[1];
  REQ->source2D = this;
  REQ->OPCODE = 201;
  REQ->voidPtr = m_hwnd.get();
  REQ->voidPtr2 = (void*)newImg;
  WxSubsystem::pushPendingWxRequest(REQ);

  MRPT_END
#endif
}

/*---------------------------------------------------------------
          showImageAndPoints
 ---------------------------------------------------------------*/
void CDisplayWindow::showImageAndPoints(
    const CImage& img,
    const CVectorFloat& x_,
    const CVectorFloat& y_,
    const TColor& color,
    bool showNumbers)
{
  std::vector<float> x(x_.size()), y(y_.size());
  for (size_t i = 0; i < x.size(); i++) x[i] = x_[i];
  for (size_t i = 0; i < y.size(); i++) y[i] = y_[i];
  showImageAndPoints(img, x, y, color, showNumbers);
}

void CDisplayWindow::showImageAndPoints(
    [[maybe_unused]] const CImage& img,
    [[maybe_unused]] const std::vector<float>& x,
    [[maybe_unused]] const std::vector<float>& y,
    [[maybe_unused]] const TColor& color,
    [[maybe_unused]] bool showNumbers)
{
#if MRPT_HAS_WXWIDGETS
  MRPT_START
  ASSERT_(x.size() == y.size());

  CImage imgColor = img.colorImage();  // Create a colorimage
  for (size_t i = 0; i < x.size(); i++)
  {
    imgColor.drawMark(round(x[i]), round(y[i]), color, '+');

    if (showNumbers)
    {
      char buf[15];
      mrpt::system::os::sprintf(buf, 15, "%d", int(i));
      imgColor.textOut(round(x[i]) - 10, round(y[i]), buf, color);
    }
  }  // end-for
  showImage(imgColor);
  MRPT_END
#endif
}

/*---------------------------------------------------------------
          plot
 ---------------------------------------------------------------*/
void CDisplayWindow::plot(const CVectorFloat& x, const CVectorFloat& y)
{
  MRPT_START

  ASSERT_(x.size() == y.size());

  const int ox = 40;
  const int oy = 40;

  // Suboptimal but...
  CImage imgColor(640, 480, mrpt::img::CH_RGB);
  // Draw axis:
  imgColor.filledRectangle(0, 0, 640, 480, TColor(255, 255, 255));
  imgColor.line(40, 40, 560, 40, TColor::black(), 3);
  imgColor.line(40, 40, 40, 440, TColor::black(), 3);
  imgColor.line(560, 40, 555, 45, TColor::black(), 3);
  imgColor.line(560, 40, 555, 35, TColor::black(), 3);
  imgColor.line(40, 440, 35, 435, TColor::black(), 3);
  imgColor.line(40, 440, 45, 435, TColor::black(), 3);

  // imgColor.textOut( 550, 25, "x", TColor::black );
  // imgColor.textOut( 25, 430, "y", TColor::black );

  CVectorFloat::const_iterator itx, ity;
  CVectorFloat::const_iterator itymx, itymn;
  itymx = std::max_element(y.begin(), y.end());
  itymn = std::min_element(y.begin(), y.end());
  float px = (x[x.size() - 1] - x[0]) / 520;
  float py = (*itymx - *itymn) / 400;

  float tpxA = 0, tpyA = 0;

  for (itx = x.begin(), ity = y.begin(); itx != x.end(); ++itx, ++ity)
  {
    float tpx = (*itx - x[0]) / px + ox;
    float tpy = (*ity - *itymn) / py + oy;
    imgColor.drawMark(round(tpx), round(tpy), TColor(255, 0, 0), 'x');
    if (itx != x.begin())
      imgColor.line(round(tpxA), round(tpyA), round(tpx), round(tpy), TColor(0, 0, 255), 3);
    tpxA = tpx;
    tpyA = tpy;
  }  // end for

  showImage(imgColor);

  MRPT_END
}

/*---------------------------------------------------------------
          plot
 ---------------------------------------------------------------*/
void CDisplayWindow::plot(const CVectorFloat& y)
{
  MRPT_START

  ASSERT_(y.size() >= 0);

  const int ox = 40;
  const int oy = 40;

  // Suboptimal but...
  CImage imgColor(640, 480, mrpt::img::CH_RGB);
  // Draw axis:
  imgColor.filledRectangle(0, 0, 640, 480, TColor::white());
  imgColor.line(40, 40, 560, 40, TColor::black(), 3);
  imgColor.line(40, 40, 40, 440, TColor::black(), 3);
  imgColor.line(560, 40, 555, 45, TColor::black(), 3);
  imgColor.line(560, 40, 555, 35, TColor::black(), 3);
  imgColor.line(40, 440, 35, 435, TColor::black(), 3);
  imgColor.line(40, 440, 45, 435, TColor::black(), 3);

  imgColor.textOut(550, 25, "x", TColor::black());
  imgColor.textOut(25, 430, "y", TColor::black());

  CVectorFloat::const_iterator ity;
  CVectorFloat::const_iterator itymx, itymn;
  itymx = std::max_element(y.begin(), y.end());
  itymn = std::min_element(y.begin(), y.end());
  float px = y.size() / 520.0f;
  float py = (*itymx - *itymn) / 400.0f;
  int tpxA = 0, tpyA = 0;

  unsigned int k = 0;

  for (k = 0, ity = y.begin(); ity != y.end(); ++k, ++ity)
  {
    auto tpx = round(k / px + ox);
    auto tpy = round((*ity - *itymn) / py + oy);
    imgColor.drawMark(tpx, tpy, TColor::red(), 'x');
    if (k > 0) imgColor.line(tpxA, tpyA, tpx, tpy, TColor::blue(), 3);
    tpxA = tpx;
    tpyA = tpy;
  }  // end for

  showImage(imgColor);

  MRPT_END
}

/*---------------------------------------------------------------
          resize
 ---------------------------------------------------------------*/
void CDisplayWindow::resize(
    [[maybe_unused]] unsigned int width, [[maybe_unused]] unsigned int height)
{
#if MRPT_HAS_WXWIDGETS
  if (!isOpen())
  {
    cerr << "[CDisplayWindow::resize] Window closed!: " << m_caption << endl;
    return;
  }

  // Send a request to destroy this object:
  auto* REQ = new WxSubsystem::TRequestToWxMainThread[1];
  REQ->source2D = this;
  REQ->OPCODE = 203;
  REQ->x = width;
  REQ->y = height;
  WxSubsystem::pushPendingWxRequest(REQ);
#endif
}

/*---------------------------------------------------------------
          setPos
 ---------------------------------------------------------------*/
void CDisplayWindow::setPos([[maybe_unused]] int x, [[maybe_unused]] int y)
{
#if MRPT_HAS_WXWIDGETS
  if (!isOpen())
  {
    cerr << "[CDisplayWindow::setPos] Window closed!: " << m_caption << endl;
    return;
  }

  // Send a request to destroy this object:
  auto* REQ = new WxSubsystem::TRequestToWxMainThread[1];
  REQ->source2D = this;
  REQ->OPCODE = 202;
  REQ->x = x;
  REQ->y = y;
  WxSubsystem::pushPendingWxRequest(REQ);
#endif
}

/*---------------------------------------------------------------
          setWindowTitle
 ---------------------------------------------------------------*/
void CDisplayWindow::setWindowTitle([[maybe_unused]] const std::string& str)
{
#if MRPT_HAS_WXWIDGETS
  if (!isOpen())
  {
    cerr << "[CDisplayWindow::setWindowTitle] Window closed!: " << m_caption << endl;
    return;
  }

  // Send a request to destroy this object:
  auto* REQ = new WxSubsystem::TRequestToWxMainThread[1];
  REQ->source2D = this;
  REQ->OPCODE = 204;
  REQ->str = str;
  WxSubsystem::pushPendingWxRequest(REQ);
#endif
}
