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

#include <mrpt/config.h>
#include <mrpt/gui/gui_frwds.h>
#include <mrpt/math/CVectorDynamic.h>
#include <mrpt/math/TPoint2D.h>
#include <mrpt/opengl/opengl_fonts.h>

#include <future>
#include <map>
#include <mutex>
#include <queue>
#include <thread>

#if MRPT_HAS_WXWIDGETS

#include <wx/app.h>
#include <wx/artprov.h>
#include <wx/bitmap.h>
#include <wx/busyinfo.h>
#include <wx/colordlg.h>
#include <wx/dcmemory.h>
#include <wx/dirdlg.h>
#include <wx/filedlg.h>
#include <wx/frame.h>
#include <wx/image.h>
#include <wx/imaglist.h>
#include <wx/intl.h>
#include <wx/log.h>
#include <wx/menu.h>
#include <wx/msgdlg.h>
#include <wx/pen.h>
#include <wx/progdlg.h>
#include <wx/sizer.h>
#include <wx/statbmp.h>
#include <wx/statusbr.h>
#include <wx/string.h>
#include <wx/textdlg.h>
#include <wx/timer.h>
#include <wx/toolbar.h>

// The wxMathPlot library
#include <mrpt/3rdparty/mathplot/mathplot.h>

#if 0
// The wxFreeChart library
#include <wx/axis/categoryaxis.h>
#include <wx/axis/dateaxis.h>
#include <wx/axis/numberaxis.h>
#include <wx/bars/barplot.h>
#include <wx/category/categorydataset.h>
#include <wx/category/categorysimpledataset.h>
#include <wx/chartpanel.h>
#include <wx/xy/xydataset.h>
#include <wx/xy/xyhistorenderer.h>
#include <wx/xy/xylinerenderer.h>
#include <wx/xy/xyplot.h>
#include <wx/xy/xysimpledataset.h>
#include <wx/xyz/bubbleplot.h>
#include <wx/xyz/xyzdataset.h>
#endif

#endif
#include <mrpt/gui/gui_frwds.h>

namespace mrpt::gui
{
/** This class implements the GUI thread required for the wxWidgets-based GUI.
 *  This system is employed internally by gui::CDisplayWindow and
 * gui::CDisplayWindow3D, and must be not used in any way directly by the MRPT
 * user.
 *
 *  The system works by creating a invisible wxFrame that process timer events
 * where it checks a queue of requests sent from the main MRPT thread. The
 *   requests include the creation, deletion,... of windows (2D/3D). In that
 * way, just one thread is required for all the GUI windows, and the wxWidgets
 *   is initialized and clean-up correctly.
 *
 *  This header should be included just from the implementation files of
 * CDisplayWindow and CDisplayWindow3D, since it uses wxWidgets classes.
 *
 *  \sa gui::CDisplayWindow, gui::CDisplayWindow3D
 * \ingroup mrpt_gui_grp
 */
class WxSubsystem
{
#if MRPT_HAS_WXWIDGETS

   public:
	/** This method must be called in the destructor of the user class FROM THE
	 * MAIN THREAD, in order to wait for the shutdown of the wx thread if this
	 * was the last open window.
	 */
	static void waitWxShutdownsIfNoWindows();

	/** Will be set to true at runtime if it's not detected a running wxApp
	 * instance.
	 *  For console apps, we'll create a new thread and run wxEntry from there.
	 *  For GUI apps (MRPT-based Windows are a part of a user wxWidget apps),
	 * we must leave the control of
	 *   message dispatching to the current main loop, so we cannot create a
	 * different threads, making things a little different (hence this
	 * variable).
	 */
	static bool isConsoleApp();

	/** An auxiliary global object used just to launch a final request to the
	 * wxSubsystem for shutdown:
	 */
	class CAuxWxSubsystemShutdowner
	{
	   public:
		CAuxWxSubsystemShutdowner();
		~CAuxWxSubsystemShutdowner();
	};

	static CAuxWxSubsystemShutdowner global_wxsubsystem_shutdown;

	/** The main frame of the wxWidgets application
	 */
	class CWXMainFrame : public wxFrame
	{
		friend void WxSubsystem::waitWxShutdownsIfNoWindows();

	   public:
		CWXMainFrame(wxWindow* parent, wxWindowID id = -1);
		~CWXMainFrame() override;

		/** Atomically increments the number of windows created with the main
		 * frame as parent.
		 * \return The updated number of windows.
		 */
		static int notifyWindowCreation();

		/** Atomically decrements the number of windows created with the main
		 * frame as parent.
		 * \return The updated number of windows (0 if the calling was the last
		 * one).
		 */
		static int notifyWindowDestruction();

		static volatile CWXMainFrame* oneInstance;

	   private:
		wxTimer* m_theTimer;

		void OnTimerProcessRequests(wxTimerEvent& event);

		DECLARE_EVENT_TABLE()

	};	// end class CWXMainFrame

	struct TWxMainThreadData
	{
		/** The thread ID of wxMainThread, or 0 if it is not running. */
		std::thread m_wxMainThreadId;
		/** This is signaled when wxMainThread is ready. */
		std::promise<void> m_semWxMainThreadReady;
		std::promise<void> m_done;
		/** The critical section for accessing "m_wxMainThreadId" */
		std::mutex m_csWxMainThreadId;
	};

	static TWxMainThreadData& GetWxMainThreadInstance();

	/**  This will be the "MAIN" of wxWidgets: It starts an application object
	 * and does not end until all the windows are closed.
	 *   Only one instance of this thread can be running at a given instant, no
	 * matter how many windows are open.
	 */
	static void wxMainThread();

	/** The data structure for each inter-thread request:
	 */
	struct TRequestToWxMainThread
	{
		TRequestToWxMainThread() = default;

		/** Only one of source* can be non-nullptr, indicating the class that
		 * generated the request. */
		mrpt::gui::CDisplayWindow* source2D{nullptr};

		/** Only one of source* can be non-nullptr, indicating the class that
		 * generated the request. */
		mrpt::gui::CDisplayWindow3D* source3D{nullptr};

		/** Only one of source* can be non-nullptr, indicating the class that
		 * generated the request. */
		mrpt::gui::CDisplayWindowPlots* sourcePlots{nullptr};

		/** Only one of source* can be non-nullptr, indicating the class that
		 * generated the request. */
		bool sourceCameraSelectDialog{false};

		/** Parameters, depending on OPCODE.
		 */
		std::string str;

		/** Parameters, depending on OPCODE.
		 */
		void *voidPtr{nullptr}, *voidPtr2{nullptr};
		int x{400}, y{400};
		bool boolVal{false};
		mrpt::math::CVectorFloat vector_x, vector_y;
		std::string plotName;

		/** Valid codes are:
		 *  For CDisplayWindow:
		 *     - 200: Create a new 2D window, with caption "str" and initial
		 *size "x" & "y", and save the "wxFrame*" in the "void**" passed in
		 *voidPtr.
		 *     - 201: Updates the image shown in the window, from a "wxImage*"
		 *passed in voidPtr2. The wxImage object will be freed with delete
		 *after that. voidPtr must be a "wxFrame*", a "CWindowDialog*"
		 *actually.
		 *     - 202: Set position to x,y
		 *     - 203: Change size to x,y
		 *     - 204: Change title to "str"
		 *     - 299: Delete the window associated with this source object.
		 *
		 *  For CDisplayWindow3D:
		 *     - 300: Create a new 3D window, with caption "str" and initial
		 *size "x" & "y", and save the "wxFrame*" in the "void**" passed in
		 *voidPtr.
		 *     - 302: Set position to x,y
		 *     - 303: Change size to x,y
		 *     - 304: Change title to "str"
		 *		- 350: Force refresh
		 *		- [Removed in MRPT2] 360: Add a 2D text message:
		 *		- [Removed in MRPT2] 361: Clear all 2D text messages.
		 *		- [Removed in MRPT2] 362: Add a 2D text message (vector font)
		 *		- 370: Change min/max range: min=vector_x[0], max=vector_x[1]
		 *     - 399: Delete the window associated with this source object.
		 *
		 *  For CDisplayWindowPlots:
		 *     - 400: Create a new Plots window, with caption "str" and initial
		 *size "x" & "y",and save the "wxFrame*" in the "void**" passed in
		 *voidPtr.
		 *     - 402: Set position to x,y
		 *     - 403: Change size to x,y
		 *     - 404: Change title to "str"
		 *     - 499: Delete the window associated with this source object.
		 *		- 410: Depending on "boolVal", enable/disable the mouse-zoom &
		 *pan
		 *		- 411: Depending on "boolVal", enable/disable the aspect ratio
		 *fix
		 *		- 412: Zoom over a rectangle vectorx[0-1] & vectory[0-1]
		 *		- 413: Axis fit, with aspect ratio fix to boolVal.
		 *		- 414: Clear all plot objects.
		 *		- 420: Add/update a 2D line/points plot: x/y data=
		 *vector_x/vector_y, format string=str, plot name =plotName.
		 *		- 421: Add/update a 2D ellipse: format string=str, plot name
		 *=plotName, vector_x[0,1]:X/Y center, vector_x[2]:quantiles,
		 *vector_y[0,1,2]: Covariance matrix entries 00,11,01,
		 *boolVal=showName?
		 *		- 422: Add/update a bitmap: plot name =plotName,
		 *vector_x[0,1]:X/Y
		 *corner, vector_x[2,3]: X/Y widths, voidPtr2: pointer to a newly
		 *created wxImage with the bitmap.
		 *		- 440: Insert submenu in the popup menu. plotName=menu label,
		 *x=user-defined ID.
		 *		- 700: Shows a camera-pick-dialog and wait for user selection.
		 *"voidPtr" must point to a CSemaphore, which will be signaled twice
		 *(1st upon construction, 2nd upon dialog close); voidPtr2 must point
		 *to a "mrpt::gui::CPanelCameraSelection*" which will be filled with
		 *the selection (the panel must be deleted by the caller)
		 *
		 *		- 800: Executes `userFunction` in the GUI thread.
		 */
		int OPCODE;

		std::function<void(void)> userFunction;
	};

	/** Thread-safe method to return the next pending request, or nullptr if
	 * there is none (After usage, FREE the memory!)
	 */
	static TRequestToWxMainThread* popPendingWxRequest();

	/** Thread-safe method to insert a new pending request (The memory must be
	 * dinamically allocated with "new T[1]", will be freed by receiver.)
	 */
	static void pushPendingWxRequest(TRequestToWxMainThread* data);

	/** Thread-safe method to create one single instance of the main wxWidgets
	 * thread: it will create the thread only if it is not running yet.
	 */
	static bool createOneInstanceMainThread();

	static wxBitmap getMRPTDefaultIcon();
#endif
};	// End of class def.

#if MRPT_HAS_WXWIDGETS

/** The wx dialog for gui::CDisplayWindow
 */
class CWindowDialog : public wxFrame
{
   public:
	/** A custom control to display the bitmap and avoid flicker
	 */
	class wxMRPTImageControl : public wxPanel
	{
	   protected:
		std::unique_ptr<wxBitmap> m_img;
		std::mutex m_img_cs;
		CDisplayWindow* m_win2D = nullptr;

	   public:
		wxMRPTImageControl(
			wxWindow* parent, wxWindowID winID, int x, int y, int width,
			int height);
		~wxMRPTImageControl() override;

		wxPoint m_last_mouse_point, m_last_mouse_click;
		// std::mutex	m_mouse_cs;

		/** Assigns this image. This object has the ownship of the image and
		 * will delete it when appropriate. */
		void AssignImage(wxBitmap* img);
		void GetBitmap(wxBitmap& bmp);

		void OnPaint(wxPaintEvent& ev);
		void OnMouseMove(wxMouseEvent& ev);
		void OnMouseClick(wxMouseEvent& ev);
		void OnChar(wxKeyEvent& ev);

		void OnEraseBackground(wxEraseEvent& ev)
		{ /* Do nothing */
		}
	};

   public:
	CWindowDialog(
		CDisplayWindow* win2D, WxSubsystem::CWXMainFrame* parent,
		wxWindowID id = -1,
		const std::string& caption = std::string("[MRPT-CDisplayWindow]"),
		wxSize initialSize = wxDefaultSize);
	~CWindowDialog() override;

	CDisplayWindow* m_win2D = nullptr;
	WxSubsystem::CWXMainFrame* m_mainFrame;

	// wxStaticBitmap      *m_image;
	wxMRPTImageControl* m_image;

	static const long ID_IMAGE_BITMAP;

   private:
	void OnClose(wxCloseEvent& event);
	void OnMenuClose(wxCommandEvent& event);
	void OnMenuAbout(wxCommandEvent& event);
	void OnMenuSave(wxCommandEvent& event);
	void OnChar(wxKeyEvent& event);
	void OnKeyDown(wxKeyEvent& event);
	void OnResize(wxSizeEvent& event);
	void OnMouseDown(wxMouseEvent& event);
	void OnMouseMove(wxMouseEvent& event);

	DECLARE_EVENT_TABLE()
};	// end class CWindowDialog

class C3DWindowDialog : public wxFrame
{
	friend class gui::CMyGLCanvas_DisplayWindow3D;

   public:
	C3DWindowDialog(
		CDisplayWindow3D* win3D, WxSubsystem::CWXMainFrame* parent,
		wxWindowID id = -1,
		const std::string& caption = std::string("[MRPT-CDisplayWindow3D]"),
		wxSize initialSize = wxDefaultSize);
	~C3DWindowDialog() override;

	CDisplayWindow3D* m_win3D;
	WxSubsystem::CWXMainFrame* m_mainFrame;

	CMyGLCanvas_DisplayWindow3D* m_canvas;

   private:
	void OnClose(wxCloseEvent& event);
	void OnMenuClose(wxCommandEvent& event);
	void OnMenuAbout(wxCommandEvent& event);
	void OnChar(wxKeyEvent& event);
	void OnResize(wxSizeEvent& event);

	static const long ID_MENUITEM1;
	static const long ID_MENUITEM2;

	DECLARE_EVENT_TABLE()
};

/** The wx dialog for gui::CDisplayWindowPlots
 */
class CWindowDialogPlots : public wxFrame
{
   public:
	CWindowDialogPlots(
		CDisplayWindowPlots* winPlots, WxSubsystem::CWXMainFrame* parent,
		wxWindowID id = -1,
		const std::string& caption = std::string("[MRPT-CDisplayWindowPlots]"),
		wxSize initialSize = wxDefaultSize);
	~CWindowDialogPlots() override;

	CDisplayWindowPlots* m_winPlots;
	WxSubsystem::CWXMainFrame* m_mainFrame;

	mpWindow* m_plot;
	// wxChartPanel 			*m_chartPanel;
	static const long ID_PLOT;
	static const long ID_MENU_PRINT;
	/** to know whether to insert a separator the first time. */
	bool m_firstSubmenu;
	/** wxIDs to user IDs for submenus. */
	std::map<long, long> m_ID2ID;
	/** In graph coords */
	mrpt::math::TPoint2D m_curCursorPos;
	/** In pixels */
	wxPoint m_last_mouse_point;

	void OnMenuSelected(wxCommandEvent& ev);
	void OnMouseMove(wxMouseEvent& event);

	/** Redirected from CDisplayWindowPlots::plot
	 */
	void plot(
		const mrpt::math::CVectorFloat& x, const mrpt::math::CVectorFloat& y,
		const std::string& lineFormat, const std::string& plotName);

	/** Redirected from CDisplayWindowPlots::plotEllipse
	 */
	void plotEllipse(
		const mrpt::math::CVectorFloat& x, const mrpt::math::CVectorFloat& y,
		const std::string& lineFormat, const std::string& plotName,
		bool showName = false);

	/** Redirected from CDisplayWindowPlots::image
	 */
	void image(
		void* theWxImage, float x0, float y0, float w, float h,
		const std::string& plotName);

   private:
	void OnClose(wxCloseEvent& event);
	void OnMenuPrint(wxCommandEvent& event);
	void OnMenuClose(wxCommandEvent& event);
	void OnMenuAbout(wxCommandEvent& event);
	void OnChar(wxKeyEvent& event);
	void OnResize(wxSizeEvent& event);
	void OnMouseDown(wxMouseEvent& event);

	DECLARE_EVENT_TABLE()
};	// end class CWindowDialog

#endif

}  // namespace mrpt::gui
