/* +------------------------------------------------------------------------+
   |                     Mobile Robot Programming Toolkit (MRPT)            |
   |                          https://www.mrpt.org/                         |
   |                                                                        |
   | Copyright (c) 2005-2022, 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 <wx/app.h>
#include <wx/string.h>
#include <wx/textctrl.h>
#include <wx/thread.h>

#include <cstdio>
#include <functional>
#include <iostream>
#include <streambuf>

#include "wx28-fixes.h"

/** This auxiliary class redirects the output sent to a streambuf to a
 * wxTextCtrl object.
 *  Uses code from http://www.devmaster.net/forums/showthread.php?t=7037
 *  Jose Luis Blanco - Dec 2007
 *  NOTE (10-Aug-2009): Added thread-safe support:
 *    We cannot write in a wxTextCtrl from a thread different than the main wx
 * one,
 *    so if this object will be used by several threads, set "m_threadSafe" to
 * true.
 *    In this mode, the object will NEVER write the text to the text control,
 * unless
 *    the method "dumpNow()" is explicitly called FROM THE MAIN THREAD.
 */
class CMyRedirector : public std::streambuf
{
   protected:
	wxTextCtrl* m_txt;
	std::streambuf* sbOld;
	std::streambuf* sbOldErr;
	const bool m_yieldApplication;
	const bool m_also_cerr;
	const bool m_threadSafe;
	const bool m_also_to_cout_cerr;

	wxCriticalSection m_cs;
	std::string m_strbuf;
	std::vector<char> m_buf;

   public:
	CMyRedirector(
		wxTextCtrl* obj, bool yieldApplication = false, int bufferSize = 3000,
		bool also_cerr = false, bool threadSafe = false,
		bool also_to_cout_cerr = false)
		: m_txt(obj),
		  m_yieldApplication(yieldApplication),
		  m_also_cerr(also_cerr),
		  m_threadSafe(threadSafe),
		  m_also_to_cout_cerr(also_to_cout_cerr)
	{
		if (bufferSize)
		{
			m_buf.resize(bufferSize);
			setp(m_buf.data(), m_buf.data() + bufferSize);
		}
		else
			setp(nullptr, nullptr);

		// Redirect:
		sbOld = std::cout.rdbuf();
		std::cout.rdbuf(this);

		if (m_also_cerr)
		{
			sbOldErr = std::cerr.rdbuf();
			std::cerr.rdbuf(this);
		}
	}
	~CMyRedirector() override
	{
		sync();

		// Restore normal output:
		std::cout.rdbuf(sbOld);

		if (m_also_cerr) std::cerr.rdbuf(sbOldErr);
	}

	void flush() { sync(); }
	virtual void writeString(const std::string& str)
	{
		if (!m_threadSafe)
		{
			const auto s = wxString(str);

#if wxCHECK_VERSION(3, 0, 0) && !defined(__APPLE__)	 // OSX build error?
			m_txt->GetEventHandler()->CallAfter(&wxTextCtrl::WriteText, s);
#else
			m_txt->WriteText(s);  // bad solution, but at least compiles (and
// may work, unsafely) for old wx2.8 in Ubuntu
// precise
#endif
		}
		else
		{  // Critical section is already adquired.
			m_strbuf += str;
		}
		if (m_also_to_cout_cerr) ::printf("%s", str.c_str());
		if (m_yieldApplication && wxThread::IsMain())
			wxTheApp->Yield(true);	// Let the app. process messages
	}

	/** Writes all the stored strings to the text control (only for threadSafe
	   mode).
		CALL THIS METHOD FROM THE MAIN THREAD!
		*/
	void dumpNow()
	{
		wxCriticalSectionLocker lock(m_cs);

		if (!m_strbuf.empty())
		{
			if (m_also_to_cout_cerr) ::printf("%s", m_strbuf.c_str());
#ifdef wxUSE_UNICODE
			*m_txt << wxString(m_strbuf.c_str(), wxConvUTF8);
#else
			*m_txt << m_strbuf.c_str();
#endif
			m_strbuf.clear();
		}
	}

   private:
	int overflow(int c) override
	{
		sync();

		if (c != EOF)
		{
			wxCriticalSectionLocker lock(m_cs);
			if (pbase() == epptr())
			{
				std::string temp;
				temp += char(c);
				writeString(temp);
			}
			else
				sputc(c);
		}

		return 0;
	}

	int sync() override
	{
		wxCriticalSectionLocker lock(m_cs);

		if (pbase() != pptr())
		{
			int len = int(pptr() - pbase());
			std::string temp(pbase(), len);
			writeString(temp);
			setp(pbase(), epptr());
		}
		return 0;
	}
};
