/* +------------------------------------------------------------------------+
   |                     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          |
   +------------------------------------------------------------------------+ */

#include "CFormPlayVideo.h"

#include <wx/app.h>
#include <wx/dcclient.h>
#include <wx/dcmemory.h>
#include <wx/dirdlg.h>
#include <wx/filedlg.h>
#include <wx/msgdlg.h>

#include "xRawLogViewerMain.h"

//(*InternalHeaders(CFormPlayVideo)
#include <wx/artprov.h>
#include <wx/bitmap.h>
#include <wx/image.h>
#include <wx/intl.h>
#include <wx/string.h>
//*)

#include <mrpt/gui/WxUtils.h>

//(*IdInit(CFormPlayVideo)
const long CFormPlayVideo::ID_RADIOBUTTON1 = wxNewId();
const long CFormPlayVideo::ID_RADIOBUTTON2 = wxNewId();
const long CFormPlayVideo::ID_STATICTEXT22 = wxNewId();
const long CFormPlayVideo::ID_TEXTCTRL11 = wxNewId();
const long CFormPlayVideo::ID_BUTTON4 = wxNewId();
const long CFormPlayVideo::ID_CHECKBOX1 = wxNewId();
const long CFormPlayVideo::ID_CHECKBOX2 = wxNewId();
const long CFormPlayVideo::ID_CHECKBOX3 = wxNewId();
const long CFormPlayVideo::ID_CHECKBOX4 = wxNewId();
const long CFormPlayVideo::ID_STATICTEXT8 = wxNewId();
const long CFormPlayVideo::ID_SPINCTRL2 = wxNewId();
const long CFormPlayVideo::ID_STATICTEXT6 = wxNewId();
const long CFormPlayVideo::ID_COMBOBOX1 = wxNewId();
const long CFormPlayVideo::ID_BUTTON2 = wxNewId();
const long CFormPlayVideo::ID_BUTTON3 = wxNewId();
const long CFormPlayVideo::ID_STATICTEXT7 = wxNewId();
const long CFormPlayVideo::ID_TEXTCTRL2 = wxNewId();
const long CFormPlayVideo::ID_BUTTON5 = wxNewId();
const long CFormPlayVideo::ID_SLIDER1 = wxNewId();
const long CFormPlayVideo::ID_STATICTEXT1 = wxNewId();
const long CFormPlayVideo::ID_SPINCTRL1 = wxNewId();
const long CFormPlayVideo::ID_BUTTON1 = wxNewId();
const long CFormPlayVideo::ID_STATICTEXT3 = wxNewId();
const long CFormPlayVideo::ID_STATICTEXT5 = wxNewId();
const long CFormPlayVideo::ID_BITMAPBUTTON1 = wxNewId();
const long CFormPlayVideo::ID_STATICTEXT4 = wxNewId();
const long CFormPlayVideo::ID_BITMAPBUTTON2 = wxNewId();
const long CFormPlayVideo::ID_STATICTEXT2 = wxNewId();
const long CFormPlayVideo::ID_BITMAPBUTTON3 = wxNewId();
const long CFormPlayVideo::ID_PANEL3 = wxNewId();
const long CFormPlayVideo::ID_PANEL4 = wxNewId();
const long CFormPlayVideo::ID_PANEL2 = wxNewId();
//*)

BEGIN_EVENT_TABLE(CFormPlayVideo, wxDialog)
//(*EventTable(CFormPlayVideo)
//*)
END_EVENT_TABLE()

// Global variables:
#include <mrpt/gui/WxUtils.h>
#include <mrpt/io/CFileGZInputStream.h>
#include <mrpt/obs/CObservation3DRangeScan.h>
#include <mrpt/obs/CObservationImage.h>
#include <mrpt/obs/CObservationStereoImages.h>
#include <mrpt/serialization/CArchive.h>
#include <mrpt/system/filesystem.h>

using namespace mrpt;
using namespace mrpt::obs;
using namespace mrpt::opengl;
using namespace mrpt::system;
using namespace mrpt::math;
using namespace mrpt::img;
using namespace mrpt::io;
using namespace mrpt::serialization;
using namespace std;

extern TTimeStamp rawlog_first_timestamp;

std::vector<CObservation::Ptr> displayedImgs(3);

CFormPlayVideo::CFormPlayVideo(wxWindow* parent, wxWindowID id)
{
	WX_START_TRY

	m_nowPlaying = false;
	firstFit = true;

	//(*Initialize(CFormPlayVideo)
	wxFlexGridSizer* FlexGridSizer4;
	wxFlexGridSizer* FlexGridSizer10;
	wxFlexGridSizer* FlexGridSizer3;
	wxFlexGridSizer* FlexGridSizer5;
	wxFlexGridSizer* FlexGridSizer9;
	wxFlexGridSizer* FlexGridSizer7;
	wxFlexGridSizer* FlexGridSizer12;
	wxFlexGridSizer* FlexGridSizer11;

	Create(
		parent, wxID_ANY, _("Play images in a Rawlog as a video"),
		wxDefaultPosition, wxDefaultSize,
		wxCAPTION | wxDEFAULT_DIALOG_STYLE | wxSYSTEM_MENU | wxRESIZE_BORDER |
			wxCLOSE_BOX | wxMAXIMIZE_BOX,
		_T("wxID_ANY"));
	FlexGridSizer1 = new wxFlexGridSizer(5, 1, 0, 0);
	FlexGridSizer1->AddGrowableCol(0);
	FlexGridSizer1->AddGrowableRow(4);
	StaticBoxSizer1 = new wxStaticBoxSizer(wxHORIZONTAL, this, _("Apply to:"));
	BoxSizer4 = new wxBoxSizer(wxVERTICAL);
	BoxSizer5 = new wxBoxSizer(wxHORIZONTAL);
	FlexGridSizer6 = new wxFlexGridSizer(2, 4, 0, 0);
	FlexGridSizer6->AddGrowableCol(2);
	rbLoaded = new wxRadioButton(
		this, ID_RADIOBUTTON1, _("Loaded rawlog"), wxDefaultPosition,
		wxDefaultSize, 0, wxDefaultValidator, _T("ID_RADIOBUTTON1"));
	rbLoaded->SetValue(true);
	FlexGridSizer6->Add(
		rbLoaded, 1, wxALL | wxALIGN_LEFT | wxALIGN_CENTER_VERTICAL, 5);
	FlexGridSizer6->Add(-1, -1, 1, wxEXPAND, 5);
	FlexGridSizer6->Add(-1, -1, 1, wxEXPAND, 5);
	FlexGridSizer6->Add(-1, -1, 1, wxEXPAND, 5);
	rbFile = new wxRadioButton(
		this, ID_RADIOBUTTON2, _("Rawlog in file:"), wxDefaultPosition,
		wxDefaultSize, 0, wxDefaultValidator, _T("ID_RADIOBUTTON2"));
	FlexGridSizer6->Add(
		rbFile, 1, wxALL | wxALIGN_LEFT | wxALIGN_CENTER_VERTICAL, 5);
	StaticText22 = new wxStaticText(
		this, ID_STATICTEXT22, _("Input file:"), wxDefaultPosition,
		wxDefaultSize, 0, _T("ID_STATICTEXT22"));
	FlexGridSizer6->Add(
		StaticText22, 1, wxALL | wxALIGN_RIGHT | wxALIGN_CENTER_VERTICAL, 5);
	edFile = new wxTextCtrl(
		this, ID_TEXTCTRL11, wxEmptyString, wxDefaultPosition, wxDefaultSize, 0,
		wxDefaultValidator, _T("ID_TEXTCTRL11"));
	FlexGridSizer6->Add(edFile, 1, wxEXPAND, 5);
	btnPickInput = new wxButton(
		this, ID_BUTTON4, _("Select..."), wxDefaultPosition, wxDefaultSize, 0,
		wxDefaultValidator, _T("ID_BUTTON4"));
	FlexGridSizer6->Add(
		btnPickInput, 1,
		wxALL | wxALIGN_CENTER_HORIZONTAL | wxALIGN_CENTER_VERTICAL, 0);
	BoxSizer5->Add(FlexGridSizer6, 1, wxEXPAND, 0);
	BoxSizer4->Add(BoxSizer5, 1, wxEXPAND, 0);
	StaticBoxSizer1->Add(BoxSizer4, 1, wxEXPAND, 0);
	FlexGridSizer1->Add(StaticBoxSizer1, 1, wxEXPAND, 0);
	FlexGridSizer9 = new wxFlexGridSizer(3, 3, 0, 0);
	cbOrderByYaw = new wxCheckBox(
		this, ID_CHECKBOX1,
		_("Order images by Yaw (<0:left,=0:middle,>0:right)"),
		wxDefaultPosition, wxDefaultSize, 0, wxDefaultValidator,
		_T("ID_CHECKBOX1"));
	cbOrderByYaw->SetValue(false);
	FlexGridSizer9->Add(
		cbOrderByYaw, 1,
		wxALL | wxALIGN_CENTER_HORIZONTAL | wxALIGN_CENTER_VERTICAL, 5);
	cbOrderByY = new wxCheckBox(
		this, ID_CHECKBOX2, _("Order by \'y\' (<0: left, >0:right)"),
		wxDefaultPosition, wxDefaultSize, 0, wxDefaultValidator,
		_T("ID_CHECKBOX2"));
	cbOrderByY->SetValue(false);
	FlexGridSizer9->Add(
		cbOrderByY, 1,
		wxALL | wxALIGN_CENTER_HORIZONTAL | wxALIGN_CENTER_VERTICAL, 5);
	cbReduceLarge = new wxCheckBox(
		this, ID_CHECKBOX3, _("Reduce large images (w>640px)"),
		wxDefaultPosition, wxDefaultSize, 0, wxDefaultValidator,
		_T("ID_CHECKBOX3"));
	cbReduceLarge->SetValue(true);
	FlexGridSizer9->Add(
		cbReduceLarge, 1,
		wxALL | wxALIGN_CENTER_HORIZONTAL | wxALIGN_CENTER_VERTICAL, 5);
	cbDrawStereoRules = new wxCheckBox(
		this, ID_CHECKBOX4, _("Draw horizontal rules\?"), wxDefaultPosition,
		wxDefaultSize, 0, wxDefaultValidator, _T("ID_CHECKBOX4"));
	cbDrawStereoRules->SetValue(false);
	FlexGridSizer9->Add(
		cbDrawStereoRules, 1,
		wxALL | wxALIGN_CENTER_HORIZONTAL | wxALIGN_CENTER_VERTICAL, 5);
	StaticText3 = new wxStaticText(
		this, ID_STATICTEXT8, _("Rule vertical spacing (pixels):"),
		wxDefaultPosition, wxDefaultSize, 0, _T("ID_STATICTEXT8"));
	FlexGridSizer9->Add(
		StaticText3, 1, wxALL | wxALIGN_RIGHT | wxALIGN_CENTER_VERTICAL, 5);
	edHorzRuleSpace = new wxSpinCtrl(
		this, ID_SPINCTRL2, _T("50"), wxDefaultPosition, wxDefaultSize, 0, 3,
		1000, 50, _T("ID_SPINCTRL2"));
	edHorzRuleSpace->SetValue(_T("50"));
	FlexGridSizer9->Add(
		edHorzRuleSpace, 1,
		wxALL | wxALIGN_CENTER_HORIZONTAL | wxALIGN_CENTER_VERTICAL, 5);
	StaticText2 = new wxStaticText(
		this, ID_STATICTEXT6,
		_("Directory for delayed-load (external) images:"), wxDefaultPosition,
		wxDefaultSize, 0, _T("ID_STATICTEXT6"));
	FlexGridSizer9->Add(
		StaticText2, 1, wxALL | wxALIGN_RIGHT | wxALIGN_CENTER_VERTICAL, 5);
	cbImageDirs = new wxComboBox(
		this, ID_COMBOBOX1, wxEmptyString, wxDefaultPosition, wxDefaultSize, 0,
		nullptr, wxCB_READONLY | wxCB_DROPDOWN, wxDefaultValidator,
		_T("ID_COMBOBOX1"));
	FlexGridSizer9->Add(
		cbImageDirs, 1,
		wxALL | wxALIGN_CENTER_HORIZONTAL | wxALIGN_CENTER_VERTICAL, 5);
	FlexGridSizer1->Add(FlexGridSizer9, 1, wxEXPAND, 0);
	FlexGridSizer8 = new wxFlexGridSizer(2, 7, 0, 0);
	FlexGridSizer8->AddGrowableCol(5);
	btnPlay = new wxButton(
		this, ID_BUTTON2, _("Play"), wxDefaultPosition, wxDefaultSize, 0,
		wxDefaultValidator, _T("ID_BUTTON2"));
	FlexGridSizer8->Add(
		btnPlay, 1, wxALL | wxALIGN_CENTER_HORIZONTAL | wxALIGN_CENTER_VERTICAL,
		5);
	btnStop = new wxButton(
		this, ID_BUTTON3, _("Stop"), wxDefaultPosition, wxDefaultSize, 0,
		wxDefaultValidator, _T("ID_BUTTON3"));
	btnStop->Disable();
	FlexGridSizer8->Add(
		btnStop, 1, wxALL | wxALIGN_LEFT | wxALIGN_CENTER_VERTICAL, 5);
	FlexGridSizer8->Add(-1, -1, 1, wxEXPAND, 5);
	StaticText6 = new wxStaticText(
		this, ID_STATICTEXT7, _("Additional delay (ms):"), wxDefaultPosition,
		wxDefaultSize, 0, _T("ID_STATICTEXT7"));
	FlexGridSizer8->Add(
		StaticText6, 1,
		wxALL | wxALIGN_CENTER_HORIZONTAL | wxALIGN_CENTER_VERTICAL, 5);
	edDelay = new wxTextCtrl(
		this, ID_TEXTCTRL2, _("10"), wxDefaultPosition, wxSize(61, 21), 0,
		wxDefaultValidator, _T("ID_TEXTCTRL2"));
	FlexGridSizer8->Add(
		edDelay, 1, wxALL | wxALIGN_CENTER_HORIZONTAL | wxALIGN_CENTER_VERTICAL,
		5);
	FlexGridSizer8->Add(-1, -1, 1, wxEXPAND, 5);
	btnClose = new wxButton(
		this, ID_BUTTON5, _("CLOSE"), wxDefaultPosition, wxDefaultSize, 0,
		wxDefaultValidator, _T("ID_BUTTON5"));
	FlexGridSizer8->Add(
		btnClose, 1,
		wxALL | wxALIGN_CENTER_HORIZONTAL | wxALIGN_CENTER_VERTICAL, 5);
	FlexGridSizer1->Add(FlexGridSizer8, 1, wxEXPAND, 0);
	FlexGridSizer4 = new wxFlexGridSizer(2, 1, 0, 0);
	FlexGridSizer4->AddGrowableCol(0);
	FlexGridSizer5 = new wxFlexGridSizer(1, 1, 0, 0);
	FlexGridSizer5->AddGrowableCol(0);
	progressBar = new wxSlider(
		this, ID_SLIDER1, 0, 0, 100, wxDefaultPosition, wxDefaultSize, 0,
		wxDefaultValidator, _T("ID_SLIDER1"));
	FlexGridSizer5->Add(progressBar, 1, wxEXPAND, 5);
	FlexGridSizer4->Add(FlexGridSizer5, 1, wxEXPAND, 0);
	FlexGridSizer7 = new wxFlexGridSizer(1, 4, 0, 0);
	FlexGridSizer7->AddGrowableCol(3);
	StaticText1 = new wxStaticText(
		this, ID_STATICTEXT1, _("Rawlog index:"), wxDefaultPosition,
		wxDefaultSize, 0, _T("ID_STATICTEXT1"));
	FlexGridSizer7->Add(
		StaticText1, 1,
		wxALL | wxALIGN_CENTER_HORIZONTAL | wxALIGN_CENTER_VERTICAL, 5);
	edIndex = new wxSpinCtrl(
		this, ID_SPINCTRL1, _T("0"), wxDefaultPosition, wxDefaultSize, 0, 0,
		100, 0, _T("ID_SPINCTRL1"));
	edIndex->SetValue(_T("0"));
	FlexGridSizer7->Add(
		edIndex, 1, wxALL | wxALIGN_CENTER_HORIZONTAL | wxALIGN_CENTER_VERTICAL,
		5);
	btnJump = new wxButton(
		this, ID_BUTTON1, _("Jump"), wxDefaultPosition, wxDefaultSize, 0,
		wxDefaultValidator, _T("ID_BUTTON1"));
	FlexGridSizer7->Add(
		btnJump, 1, wxALL | wxALIGN_CENTER_HORIZONTAL | wxALIGN_CENTER_VERTICAL,
		5);
	lbProgress = new wxStaticText(
		this, ID_STATICTEXT3, _("(Progress)"), wxDefaultPosition, wxDefaultSize,
		0, _T("ID_STATICTEXT3"));
	FlexGridSizer7->Add(
		lbProgress, 1, wxALL | wxALIGN_LEFT | wxALIGN_CENTER_VERTICAL, 5);
	FlexGridSizer4->Add(FlexGridSizer7, 1, wxEXPAND, 0);
	FlexGridSizer1->Add(FlexGridSizer4, 1, wxEXPAND, 0);
	FlexGridSizer3 = new wxFlexGridSizer(2, 3, 0, 0);
	FlexGridSizer3->AddGrowableCol(0);
	FlexGridSizer3->AddGrowableCol(1);
	FlexGridSizer3->AddGrowableCol(2);
	FlexGridSizer3->AddGrowableRow(1);
	FlexGridSizer10 = new wxFlexGridSizer(1, 2, 0, 0);
	FlexGridSizer10->AddGrowableCol(0);
	lbCam1 = new wxStaticText(
		this, ID_STATICTEXT5, _("Cam1\?\?"), wxDefaultPosition, wxDefaultSize,
		0, _T("ID_STATICTEXT5"));
	FlexGridSizer10->Add(
		lbCam1, 1, wxALL | wxALIGN_CENTER_HORIZONTAL | wxALIGN_CENTER_VERTICAL,
		5);
	btnSaveCam1 = new wxBitmapButton(
		this, ID_BITMAPBUTTON1,
		wxArtProvider::GetBitmap(
			wxART_MAKE_ART_ID_FROM_STR(_T("wxART_FILE_SAVE_AS")), wxART_BUTTON),
		wxDefaultPosition, wxDefaultSize, wxBU_AUTODRAW, wxDefaultValidator,
		_T("ID_BITMAPBUTTON1"));
	btnSaveCam1->SetDefault();
	FlexGridSizer10->Add(btnSaveCam1, 1, wxEXPAND, 5);
	FlexGridSizer3->Add(FlexGridSizer10, 1, wxEXPAND, 0);
	FlexGridSizer11 = new wxFlexGridSizer(1, 2, 0, 0);
	FlexGridSizer11->AddGrowableCol(0);
	lbCam2 = new wxStaticText(
		this, ID_STATICTEXT4, _("Cam2\?\?"), wxDefaultPosition, wxDefaultSize,
		0, _T("ID_STATICTEXT4"));
	FlexGridSizer11->Add(
		lbCam2, 1, wxALL | wxALIGN_CENTER_HORIZONTAL | wxALIGN_CENTER_VERTICAL,
		5);
	btnSaveCam2 = new wxBitmapButton(
		this, ID_BITMAPBUTTON2,
		wxArtProvider::GetBitmap(
			wxART_MAKE_ART_ID_FROM_STR(_T("wxART_FILE_SAVE_AS")), wxART_BUTTON),
		wxDefaultPosition, wxDefaultSize, wxBU_AUTODRAW, wxDefaultValidator,
		_T("ID_BITMAPBUTTON2"));
	btnSaveCam2->SetDefault();
	FlexGridSizer11->Add(btnSaveCam2, 1, wxEXPAND, 5);
	FlexGridSizer3->Add(FlexGridSizer11, 1, wxEXPAND, 0);
	FlexGridSizer12 = new wxFlexGridSizer(1, 2, 0, 0);
	FlexGridSizer12->AddGrowableCol(0);
	lbCam3 = new wxStaticText(
		this, ID_STATICTEXT2, _("Cam3\?\?"), wxDefaultPosition, wxDefaultSize,
		0, _T("ID_STATICTEXT2"));
	FlexGridSizer12->Add(
		lbCam3, 1, wxALL | wxALIGN_CENTER_HORIZONTAL | wxALIGN_CENTER_VERTICAL,
		5);
	btnSaveCam3 = new wxBitmapButton(
		this, ID_BITMAPBUTTON3,
		wxArtProvider::GetBitmap(
			wxART_MAKE_ART_ID_FROM_STR(_T("wxART_FILE_SAVE_AS")), wxART_BUTTON),
		wxDefaultPosition, wxDefaultSize, wxBU_AUTODRAW, wxDefaultValidator,
		_T("ID_BITMAPBUTTON3"));
	btnSaveCam3->SetDefault();
	FlexGridSizer12->Add(btnSaveCam3, 1, wxEXPAND, 5);
	FlexGridSizer3->Add(FlexGridSizer12, 1, wxEXPAND, 0);
	pnLeft = new wxPanel(
		this, ID_PANEL3, wxDefaultPosition, wxSize(320, 240),
		wxSUNKEN_BORDER | wxTAB_TRAVERSAL, _T("ID_PANEL3"));
	pnLeft->SetMinSize(wxSize(-1, 240));
	FlexGridSizer3->Add(pnLeft, 1, wxEXPAND, 1);
	pnRight = new wxPanel(
		this, ID_PANEL4, wxDefaultPosition, wxSize(320, 240),
		wxSUNKEN_BORDER | wxTAB_TRAVERSAL, _T("ID_PANEL4"));
	pnRight->SetMinSize(wxSize(-1, 240));
	FlexGridSizer3->Add(pnRight, 1, wxEXPAND, 1);
	pnRight2 = new wxPanel(
		this, ID_PANEL2, wxDefaultPosition, wxSize(320, 240),
		wxSUNKEN_BORDER | wxTAB_TRAVERSAL, _T("ID_PANEL2"));
	pnRight2->SetMinSize(wxSize(-1, 240));
	FlexGridSizer3->Add(pnRight2, 1, wxEXPAND, 1);
	FlexGridSizer1->Add(FlexGridSizer3, 1, wxEXPAND, 0);
	SetSizer(FlexGridSizer1);
	FlexGridSizer1->Fit(this);
	FlexGridSizer1->SetSizeHints(this);
	Center();

	Bind(
		wxEVT_RADIOBUTTON, &CFormPlayVideo::OnrbLoadedSelect, this,
		ID_RADIOBUTTON1);
	Bind(
		wxEVT_RADIOBUTTON, &CFormPlayVideo::OnrbFileSelect, this,
		ID_RADIOBUTTON2);
	Bind(wxEVT_BUTTON, &CFormPlayVideo::OnbtnPickClick, this, ID_BUTTON4);
	Bind(
		wxEVT_COMBOBOX, &CFormPlayVideo::OncbImageDirsSelect, this,
		ID_COMBOBOX1);
	Bind(wxEVT_BUTTON, &CFormPlayVideo::OnbtnPlayClick, this, ID_BUTTON2);
	Bind(wxEVT_BUTTON, &CFormPlayVideo::OnbtnStopClick, this, ID_BUTTON3);
	Bind(wxEVT_BUTTON, &CFormPlayVideo::OnbtnCloseClick, this, ID_BUTTON5);
	Bind(
		wxEVT_SCROLL_THUMBTRACK, &CFormPlayVideo::OnprogressBarCmdScrollChanged,
		this, ID_SLIDER1);
	Bind(
		wxEVT_SCROLL_CHANGED, &CFormPlayVideo::OnprogressBarCmdScrollChanged,
		this, ID_SLIDER1);
	Bind(
		wxEVT_BUTTON, &CFormPlayVideo::OnbtnSaveCam1Click, this,
		ID_BITMAPBUTTON1);
	Bind(
		wxEVT_BUTTON, &CFormPlayVideo::OnbtnSaveCam2Click, this,
		ID_BITMAPBUTTON2);
	Bind(
		wxEVT_BUTTON, &CFormPlayVideo::OnbtnSaveCam3Click, this,
		ID_BITMAPBUTTON3);
	Bind(wxEVT_INIT_DIALOG, &CFormPlayVideo::OnInit, this, wxID_ANY);
	//*)

	WX_END_TRY
}

CFormPlayVideo::~CFormPlayVideo()
{
	//(*Destroy(CFormPlayVideo)
	//*)
}

// Pick a file:
void CFormPlayVideo::OnbtnPickClick(wxCommandEvent& event)
{
	wxString caption = wxT("Choose a file to open");
	wxString wildcard =
		wxT("RawLog files (*.rawlog,*.rawlog.gz)|*.rawlog;*.rawlog.gz|All "
			"files (*.*)|*.*");

	wxString defaultDir(
		(iniFile->read_string(iniFileSect, "LastDir", ".").c_str()));

	wxString defaultFilename;
	wxFileDialog dialog(
		this, caption, defaultDir, defaultFilename, wildcard,
		wxFD_OPEN | wxFD_FILE_MUST_EXIST);

	if (dialog.ShowModal() == wxID_OK)
	{
		wxString fileName = dialog.GetPath();
		wxString filePath = dialog.GetDirectory();

		// Save the path
		try
		{
			iniFile->write(
				iniFileSect, "LastDir", std::string(filePath.mb_str()));
		}
		catch (const std::exception& e)
		{
			wxMessageBox(
				mrpt::exception_to_str(e), wxT("Exception"), wxOK, this);
		}

		// Sets the file:
		edFile->ChangeValue(fileName);
	}
}

// On stop:
void CFormPlayVideo::OnbtnStopClick(wxCommandEvent&)
{
	m_nowPlaying = false;
	btnPlay->Enable(true);
	btnStop->Enable(false);
}

// On play:
void CFormPlayVideo::OnbtnPlayClick(wxCommandEvent&)
{
	btnPlay->Enable(false);
	btnStop->Enable(true);
	m_nowPlaying = true;

	try
	{
		long delay_ms = 0;
		edDelay->GetValue().ToLong(&delay_ms);

		CFileGZInputStream* fil;

		if (rbFile->GetValue())
		{
			// Load from file:
			fil = new CFileGZInputStream(
				std::string(edFile->GetValue().mb_str()));
		}
		else
		{
			// Use the loaded rawlog:
			fil = nullptr;
		}

		size_t nImgs = 0, count = 0;

		// If we are playing from memory, continue:
		if (!fil) { count = edIndex->GetValue(); }

		progressBar->SetRange(
			0, fil ? (int)fil->getTotalBytesCount() : (int)rawlog.size());
		progressBar->SetValue(0);

		// Repeat until EOF exception or cancel.
		while (m_nowPlaying)
		{
			wxTheApp->Yield();
			CSerializable::Ptr obj;
			mrpt::Clock::time_point imgTimestamp = INVALID_TIMESTAMP;

			if (fil) { archiveFrom(*fil) >> obj; }
			else
			{
				obj = rawlog.getAsGeneric(count);
				m_idxInRawlog = count;
			}

			bool doDelay = false;

			if (auto sf = std::dynamic_pointer_cast<CSensoryFrame>(obj); sf)
			{
				doDelay = showSensoryFrame(*sf, nImgs, imgTimestamp);
			}
			else if (IS_DERIVED(*obj, CObservation))
			{
				CSensoryFrame sf2;
				sf2.insert(std::dynamic_pointer_cast<CObservation>(obj));
				doDelay = showSensoryFrame(sf2, nImgs, imgTimestamp);
			}

			// Free the loaded object!
			if (fil) obj.reset();

			// Update UI
			count++;
			// if ((count) % 10 == 0)
			{
				progressBar->SetValue(
					fil ? (int)fil->getPosition() : (int)count);
				wxString str;
				if (imgTimestamp != INVALID_TIMESTAMP)
				{
					str.sprintf(
						_("Processed: %d images. Timestamp: %f [%s UTC]"),
						static_cast<int>(nImgs),
						mrpt::Clock::toDouble(imgTimestamp),
						mrpt::system::dateTimeToString(imgTimestamp).c_str());
				}
				else
				{
					str.sprintf(
						_("Processed: %d images."), static_cast<int>(nImgs));
				}
				lbProgress->SetLabel(str);
				if (!doDelay) wxTheApp->Yield();
			}

			// if (doDelay || (count % 100)==0)
			edIndex->SetValue(count);

			// End?
			if (!fil && count >= rawlog.size()) m_nowPlaying = false;

			// Time to process stop button press and redraw image.

			if (doDelay)
			{
				wxTheApp->Yield();
				wxMilliSleep(delay_ms);
			}
		}
	}
	catch (CExceptionExternalImageNotFound& e)
	{
		wxMessageBox(
			mrpt::exception_to_str(e), _("Error with a delayed load image"),
			wxOK, this);

		if (wxYES ==
			wxMessageBox(
				(format(
					 "The current directory for relative images "
					 "is:\n%s\n\nDo you want to set it to a different one?",
					 CImage::getImagesPathBase().c_str())
					 .c_str()),
				_("Error with delayed loading image"), wxYES_NO, this))
		{
			// Change CImage::getImagesPathBase()
			wxDirDialog dirDialog(
				this, _("Choose the base directory for relative image paths"),
				CImage::getImagesPathBase().c_str(), 0, wxDefaultPosition);
			if (dirDialog.ShowModal() == wxID_OK)
			{
				CImage::setImagesPathBase(string(dirDialog.GetPath().mb_str()));
			}
		}
	}
	catch (const std::exception& e)
	{
		wxMessageBox(mrpt::exception_to_str(e), _("Exception"), wxOK, this);
	}

	btnPlay->Enable(true);
	btnStop->Enable(false);
}

void CFormPlayVideo::OnInit(wxInitDialogEvent& event)
{
	wxCommandEvent dumm;
	// Is there any rawlog loaded??
	if (!rawlog.size())
	{
		// NO: Disable "modify current":
		OnrbFileSelect(dumm);
		rbFile->SetValue(true);
		rbLoaded->Disable();
	}
	else
	{
		// Select the loaded rawlog by default:
		OnrbLoadedSelect(dumm);
		rbLoaded->SetValue(true);
	}  // end there is loaded rawlog
}

void CFormPlayVideo::OnrbLoadedSelect(wxCommandEvent& event)
{
	btnPickInput->Disable();
	edFile->Disable();

	progressBar->SetRange(0, (int)rawlog.size());
	edIndex->SetRange(0, (int)rawlog.size());
	progressBar->Enable();
	edIndex->Enable();
}

void CFormPlayVideo::OnrbFileSelect(wxCommandEvent& event)
{
	btnPickInput->Enable();
	edFile->Enable();
	progressBar->Disable();
	edIndex->Disable();
}

void CFormPlayVideo::OnbtnCloseClick(wxCommandEvent& event)
{
	m_nowPlaying = false;
	Close();
}

void CFormPlayVideo::drawHorzRules(mrpt::img::CImage& img)
{
	if (!cbDrawStereoRules->IsChecked()) return;

	img.forceLoad();
	const size_t Ay = edHorzRuleSpace->GetValue();
	const size_t h = img.getHeight();
	const size_t w = img.getWidth();

	for (size_t y = Ay; y < h; y += Ay)
		img.line(0, y, w - 1, y, mrpt::img::TColor::white());
}

bool CFormPlayVideo::showSensoryFrame(
	mrpt::obs::CSensoryFrame& SF, size_t& nImgs, Clock::time_point& timestamp)
{
	WX_START_TRY

	try
	{
		return showSensoryFrameImpl(SF, nImgs, timestamp);
	}
	catch (const std::exception& e)
	{
		// Make sure playback is stopped in the event of an error:
		wxCommandEvent ev;
		OnbtnStopClick(ev);
		throw;
	}

	WX_END_TRY
	return false;
}

bool CFormPlayVideo::showSensoryFrameImpl(
	mrpt::obs::CSensoryFrame& SF, size_t& nImgs, Clock::time_point& timestamp)
{
	auto* sf = &SF;

	bool doDelay = false;
	bool doReduceLargeImgs = cbReduceLarge->GetValue();
	bool orderByYaw = cbOrderByYaw->GetValue();
	bool orderByY = cbOrderByY->GetValue();

	// Find an image to show:
	CImage* imgShow = nullptr;

	// unload current imgs:
	for (auto& displayedImg : displayedImgs)
		if (displayedImg) displayedImg->unload();

	CImage auxImgForSubSampling;

	// Monocular images:
	{
		wxPanel* thePanel;
		wxStaticText* theLabel = nullptr;

		for (int img_idx_sf = 0; img_idx_sf < 3;
			 img_idx_sf++)	// Up to 3 images maximum:
		{
			CObservationImage::Ptr obsImg =
				sf->getObservationByClass<CObservationImage>(img_idx_sf);
			if (!obsImg) break;	 // No more images, go on...

			timestamp = obsImg->timestamp;

			// Onto which panel to draw??
			if (!orderByYaw && !orderByY)
			{
				// Sequentially
				switch (img_idx_sf)
				{
					case 0:
						thePanel = pnLeft;
						theLabel = lbCam1;
						break;
					case 1:
						thePanel = pnRight;
						theLabel = lbCam2;
						break;
					case 2:
						thePanel = pnRight2;
						theLabel = lbCam3;
						break;
					default: ASSERT_(false);
				};
			}
			else
			{
				if (orderByY)
				{
					// By Y
					if (obsImg->cameraPose.y() < 0)
					{
						thePanel = pnRight;
						theLabel = lbCam2;
					}
					else if (obsImg->cameraPose.y() > 0)
					{
						thePanel = pnLeft;
						theLabel = lbCam1;
					}
					else
					{
						thePanel = pnRight2;
						theLabel = lbCam3;
					}
				}
				else
				{
					// By yaw angle:
					if (obsImg->cameraPose.yaw() < -3.0_deg)
					{
						thePanel = pnRight2;
						theLabel = lbCam3;
					}
					else if (obsImg->cameraPose.yaw() > 3.0_deg)
					{
						thePanel = pnLeft;
						theLabel = lbCam1;
					}
					else
					{
						thePanel = pnRight;	 // Center
						theLabel = lbCam2;
					}
				}
			}

			nImgs++;

			imgShow = &obsImg->image;

			// Subsampling?
			if (doReduceLargeImgs && imgShow->getWidth() > 650)
			{
				auxImgForSubSampling =
					imgShow->scaleHalf(mrpt::img::IMG_INTERP_NN);
				imgShow = &auxImgForSubSampling;
			}

			if (firstFit)
			{
				pnLeft->SetMinSize(
					wxSize(imgShow->getWidth() + 2, imgShow->getHeight() + 2));
				pnRight->SetMinSize(
					wxSize(imgShow->getWidth() + 2, imgShow->getHeight() + 2));
				pnRight2->SetMinSize(
					wxSize(imgShow->getWidth() + 2, imgShow->getHeight() + 2));
				Fit();
				firstFit = false;
			}

			// Draw image:
			drawHorzRules(*imgShow);
			wxImage* wxIMG = mrpt::gui::MRPTImage2wxImage(*imgShow);
			imgShow->unload();	// for delayed-loaded rawlogs

			wxWindowDC dc(thePanel);
			wxMemoryDC tmpDc;
			tmpDc.SelectObjectAsSource(wxBitmap(*wxIMG));
			dc.Blit(0, 0, wxIMG->GetWidth(), wxIMG->GetHeight(), &tmpDc, 0, 0);
			delete wxIMG;

			// Set text label:
			if (theLabel)
			{
				double t = 0;
				if (obsImg->timestamp != INVALID_TIMESTAMP)
					t = mrpt::system::timeDifference(
						rawlog_first_timestamp, obsImg->timestamp);

				string s =
					format("%s [t=%.03fsec]", obsImg->sensorLabel.c_str(), t);
				theLabel->SetLabel(s.c_str());
			}

			// save:
			displayedImgs
				[thePanel == pnLeft ? 0 : (thePanel == pnRight ? 1 : 2)] =
					obsImg;

			doDelay = true;

		}  // end for 0,1,2
	}

	// Stereo images:
	{
		CObservationStereoImages::Ptr obsImg2 =
			sf->getObservationByClass<CObservationStereoImages>();

		if (obsImg2)
		{
			timestamp = obsImg2->timestamp;
			nImgs++;

			// Left:
			{
				imgShow = &obsImg2->imageLeft;

				// Subsampling?
				if (doReduceLargeImgs && imgShow->getWidth() > 650)
				{
					auxImgForSubSampling =
						imgShow->scaleHalf(mrpt::img::IMG_INTERP_NN);
					imgShow = &auxImgForSubSampling;
				}

				if (firstFit)
				{
					pnLeft->SetMinSize(wxSize(
						imgShow->getWidth() + 2, imgShow->getHeight() + 2));
					// Fit();
					// firstFit=false; // Done in the right pane below...
				}

				drawHorzRules(*imgShow);
				wxImage* wxIMG = mrpt::gui::MRPTImage2wxImage(*imgShow);
				imgShow->unload();	// for delayed-loaded rawlogs

				wxWindowDC dc(pnLeft);
				wxMemoryDC tmpDc;
				tmpDc.SelectObjectAsSource(wxBitmap(*wxIMG));
				dc.Blit(
					0, 0, wxIMG->GetWidth(), wxIMG->GetHeight(), &tmpDc, 0, 0);
				delete wxIMG;

				lbCam1->SetLabel((
					format("%s - left", obsImg2->sensorLabel.c_str()).c_str()));

				// save:
				displayedImgs[0] = obsImg2;

				doDelay = true;
			}

			// Right, if present:
			if (obsImg2->hasImageRight)
			{
				imgShow = &obsImg2->imageRight;

				// Subsampling?
				if (doReduceLargeImgs && imgShow->getWidth() > 650)
				{
					auxImgForSubSampling =
						imgShow->scaleHalf(mrpt::img::IMG_INTERP_NN);
					imgShow = &auxImgForSubSampling;
				}

				if (firstFit)
				{
					pnRight->SetMinSize(wxSize(
						imgShow->getWidth() + 2, imgShow->getHeight() + 2));
					Fit();
					firstFit = false;
				}

				drawHorzRules(*imgShow);
				wxImage* wxIMG = mrpt::gui::MRPTImage2wxImage(*imgShow);
				imgShow->unload();	// for delayed-loaded rawlogs

				wxWindowDC dc(pnRight);
				wxMemoryDC tmpDc;
				tmpDc.SelectObjectAsSource(wxBitmap(*wxIMG));
				dc.Blit(
					0, 0, wxIMG->GetWidth(), wxIMG->GetHeight(), &tmpDc, 0, 0);
				delete wxIMG;

				lbCam2->SetLabel(
					(format("%s - right", obsImg2->sensorLabel.c_str())
						 .c_str()));

				// save:
				displayedImgs[1] = obsImg2;

				doDelay = true;
			}

			// Disparity, if present:
			if (obsImg2->hasImageDisparity)
			{
				imgShow = &obsImg2->imageDisparity;

				// Subsampling?
				if (doReduceLargeImgs && imgShow->getWidth() > 650)
				{
					auxImgForSubSampling =
						imgShow->scaleHalf(mrpt::img::IMG_INTERP_NN);
					imgShow = &auxImgForSubSampling;
				}

				if (firstFit)
				{
					pnRight->SetMinSize(wxSize(
						imgShow->getWidth() + 2, imgShow->getHeight() + 2));
					Fit();
					firstFit = false;
				}

				drawHorzRules(*imgShow);
				wxImage* wxIMG = mrpt::gui::MRPTImage2wxImage(*imgShow);
				imgShow->unload();	// for delayed-loaded rawlogs

				wxWindowDC dc(pnRight);
				wxMemoryDC tmpDc;
				tmpDc.SelectObjectAsSource(wxBitmap(*wxIMG));
				dc.Blit(
					0, 0, wxIMG->GetWidth(), wxIMG->GetHeight(), &tmpDc, 0, 0);
				delete wxIMG;

				lbCam2->SetLabel(
					(format("%s - disparity", obsImg2->sensorLabel.c_str())
						 .c_str()));

				// save:
				displayedImgs[1] = obsImg2;

				doDelay = true;
			}
		}
	}

	// 3D range images:
	{
		CObservation3DRangeScan::Ptr obs3D =
			sf->getObservationByClass<CObservation3DRangeScan>();

		if (obs3D && obs3D->hasIntensityImage)
		{
			timestamp = obs3D->timestamp;
			nImgs++;

			// Intensity channel
			{
				imgShow = &obs3D->intensityImage;

				// Subsampling?
				if (doReduceLargeImgs && imgShow->getWidth() > 650)
				{
					auxImgForSubSampling =
						imgShow->scaleHalf(mrpt::img::IMG_INTERP_NN);
					imgShow = &auxImgForSubSampling;
				}

				if (firstFit)
				{
					pnLeft->SetMinSize(wxSize(
						imgShow->getWidth() + 2, imgShow->getHeight() + 2));
					// Fit();
					// firstFit=false; // Done in the right pane below...
				}

				drawHorzRules(*imgShow);
				wxImage* wxIMG = mrpt::gui::MRPTImage2wxImage(*imgShow);
				imgShow->unload();	// for delayed-loaded rawlogs

				wxWindowDC dc(pnLeft);
				wxMemoryDC tmpDc;
				tmpDc.SelectObjectAsSource(wxBitmap(*wxIMG));
				dc.Blit(
					0, 0, wxIMG->GetWidth(), wxIMG->GetHeight(), &tmpDc, 0, 0);
				delete wxIMG;

				lbCam1->SetLabel(
					(format("%s - Intensity", obs3D->sensorLabel.c_str())
						 .c_str()));

				// save:
				displayedImgs[0] = obs3D;

				doDelay = true;
			}
		}
	}

	return doDelay;
}

void CFormPlayVideo::OnprogressBarCmdScrollChanged(wxScrollEvent&)
{
	int idx = progressBar->GetValue();
	m_idxInRawlog = idx;
	mrpt::Clock::time_point imgTimestamp;
	size_t nImgs = 0;

	if (idx > 0 && idx < (int)rawlog.size())
	{
		if (rawlog.getType(idx) == CRawlog::etSensoryFrame)
		{
			CSensoryFrame::Ptr sf = rawlog.getAsObservations(idx);
			showSensoryFrame(*sf.get(), nImgs, imgTimestamp);
			edIndex->SetValue(idx);
		}
		else if (rawlog.getType(idx) == CRawlog::etObservation)
		{
			CObservation::Ptr o = rawlog.getAsObservation(idx);

			CSensoryFrame sf;
			sf.insert(o);
			showSensoryFrame(sf, nImgs, imgTimestamp);

			edIndex->SetValue(idx);
		}

		wxString str;
		if (imgTimestamp != INVALID_TIMESTAMP)
		{
			str.sprintf(
				_("Processed: %d images. Timestamp: %f [%s UTC]"),
				static_cast<int>(nImgs), mrpt::Clock::toDouble(imgTimestamp),
				mrpt::system::dateTimeToString(imgTimestamp).c_str());
		}
		else
		{
			str.sprintf(_("Processed: %d images."), static_cast<int>(nImgs));
		}
		lbProgress->SetLabel(str);
	}
}

void CFormPlayVideo::saveCamImage(int n)
{
	WX_START_TRY

	if (n < 0 || n >= (int)displayedImgs.size() || !displayedImgs[n]) return;

	wxString caption = wxT("Save image...");
	wxString wildcard =
		wxT("Image files (*.png,*.jpg,*.bmp)|*.jpg;*.bmp;*.png|All files "
			"(*.*)|*.*");
	wxString defaultDir(
		(iniFile->read_string(iniFileSect, "LastDir", ".").c_str()));

	if (IS_CLASS(*displayedImgs[n], CObservationImage))
	{
		CObservationImage::Ptr o =
			std::dynamic_pointer_cast<CObservationImage>(displayedImgs[n]);

		wxString defaultFilename =
			(format("%s_%i.jpg", o->sensorLabel.c_str(), m_idxInRawlog)
				 .c_str());
		wxFileDialog dialog(
			this, caption, defaultDir, defaultFilename, wildcard,
			wxFD_SAVE | wxFD_OVERWRITE_PROMPT);

		if (dialog.ShowModal() != wxID_OK) return;

		string fil = string(dialog.GetPath().mb_str());

		o->image.saveToFile(fil);
	}
	else if (IS_CLASS(*displayedImgs[n], CObservationStereoImages))
	{
		CObservationStereoImages::Ptr o =
			std::dynamic_pointer_cast<CObservationStereoImages>(
				displayedImgs[n]);

		wxString defaultFilename;
		switch (n)
		{
			case 0:
				defaultFilename = (format(
									   "%s_left_%i.jpg", o->sensorLabel.c_str(),
									   m_idxInRawlog)
									   .c_str());
				break;
			case 1:
				defaultFilename = (format(
									   "%s_right_%i.jpg",
									   o->sensorLabel.c_str(), m_idxInRawlog)
									   .c_str());
				break;
			case 2:
				defaultFilename = (format(
									   "%s_disp_%i.jpg", o->sensorLabel.c_str(),
									   m_idxInRawlog)
									   .c_str());
				break;
		}

		wxFileDialog dialog(
			this, caption, defaultDir, defaultFilename, wildcard,
			wxFD_SAVE | wxFD_OVERWRITE_PROMPT);

		if (dialog.ShowModal() != wxID_OK) return;

		string fil = string(dialog.GetPath().mb_str());

		CImage& im =
			(n == 2 ? o->imageDisparity
					: (n == 1 ? o->imageRight : o->imageLeft));
		im.saveToFile(fil);
	}

	WX_END_TRY
}

void CFormPlayVideo::OnbtnSaveCam1Click(wxCommandEvent&) { saveCamImage(0); }
void CFormPlayVideo::OnbtnSaveCam2Click(wxCommandEvent&) { saveCamImage(1); }
void CFormPlayVideo::OnbtnSaveCam3Click(wxCommandEvent&) { saveCamImage(2); }

void CFormPlayVideo::OncbImageDirsSelect(wxCommandEvent&)
{
	wxString dir = cbImageDirs->GetValue();
	string dirc = string(dir.mb_str());

	if (mrpt::system::fileExists(dirc)) { CImage::setImagesPathBase(dirc); }
	else
	{
		wxMessageBox(
			_("The directory:\n") + dir + _("does not exist."),
			_("External images"));
	}
}
