#include "../Headers/RumPiManagerThread.h"
#include <memory>
#include <wx/stopwatch.h>
#include <RumPi/Components/BaseComponent.h>
#include <RumPi/ComponentManager.h>
#include <RumPi/RumPi.h>
#include <RumPi/AlertManager.h>
#include <RumPi/Data/SelectedSensorReadings.h>

/**
* Builds the manager thread (joinable) and stores the UI handler. The RumPi library is initialized later, on the
* worker thread at the top of Entry() (not here), so every library call happens on the one worker thread.
*
* @author Eddie O'Hagan
* @date 8/13/2026
*
* @code
*	RumPiManagerThread* thread = new RumPiManagerThread(this);
*	thread->Run();
* @endcode
*
* @param [in] handler The UI event handler to post component updates to.
*/
RumPiManagerThread::RumPiManagerThread(wxEvtHandler* handler) : wxThread(wxTHREAD_JOINABLE)
{
	myWxEventHandler = handler;
}

/**
* Destroys the thread, waiting for it to finish first.
*
* @author Eddie O'Hagan
* @date 8/13/2026
*
* @code
*	delete thread;
* @endcode
*/
RumPiManagerThread::~RumPiManagerThread()
{
	//Delete() (not Wait()) so this is safe even if the thread is still running: Delete() signals TestDestroy and
	//joins, whereas Wait() alone would block forever on a thread still looping its while (TestDestroy() == false).
	//Call sites already Delete() first, so this is normally a no-op - it just removes the latent deadlock.
	Delete();
}

/**
* Thread body: starts the library, then loops on a fast tick (~100ms) fast-sampling the wind/motion/light sensors
* every tick, and on a slower wall-clock cadence (~2s) refreshes all components and posts their data plus the
* dashboard snapshot to the UI, until the thread is asked to exit.
*
* @author Eddie O'Hagan
* @date 8/13/2026
*
* @code
*	//Run by wxWidgets when the thread starts.
* @endcode
*
* @return The thread's exit code (always 0).
*/
wxThread::ExitCode RumPiManagerThread::Entry()
{
	//Guard the whole run (Initialize + Start + the poll loop) so any startup or fatal exception still shuts down
	//cleanly - no std::terminate across the wxThread boundary, and Stop() below still de-energizes actuators -
	//rather than crashing the app. Per-tick errors are handled inside the loop so a transient fault just logs and
	//the dashboard keeps running.
	try
	{
		//Initialize the library on this worker thread (not the constructor) so Initialize/Start/Stop all live on one
		//thread. It self-assembles its sensors from the JSON manifest during Initialize; the factory's per-entry
		//guard only catches JSON errors, so a component ctor throwing bad_alloc / a hardware setup error would
		//surface here - hence inside this guard.
		RumPi::Initialize();

		RumPi::Start(false);

		//Poll cadence. The loop ticks fast so a brief gust/motion pulse and a lighting change are caught promptly
		//(the fast sensors below); the heavier full refresh + UI push run on a slower cadence. Timing that cadence
		//off a wall clock (rather than counting fixed ticks) keeps its spacing a steady ~2s even when a tick's
		//sensor reads run long, which a bare "every 20 ticks" could not guarantee.
		const int fastTickIntervalMilliseconds = 100;
		const long fullUpdateIntervalMilliseconds = 2000;

		//Elapsed time since the last full update; the stopwatch starts now, so the first full update fires ~2s in.
		wxStopWatch timeSinceLastFullUpdate;

		//Keep looping until the thread requests exit.
		while (TestDestroy() == false)
		{
			//A single bad tick - a transient sensor read that throws, or a std::bad_alloc building the JSON
			//snapshot - shouldn't take the whole dashboard down. Log it and keep polling; sensing stays alive, and a
			//persistent fault keeps logging (which is itself the signal). A real shutdown still exits via
			//TestDestroy() and runs Stop() below.
			try
			{
				//Fast-sample the wind, motion, and light sensors every tick (~100ms). Wind/motion so a brief gust or
				//motion pulse lands in the sensor's peak/latch; light so the TSL2591's one-step-per-read adaptive gain
				//settles in a fraction of a second after a lighting change instead of crawling over several 2s snapshots.
				//The remaining sensors stay on the slower pass below.
				RumPi::UpdateComponentsOfType(RumPi::EComponentType::E_QSFS01);
				RumPi::UpdateComponentsOfType(RumPi::EComponentType::E_HCSR501);
				RumPi::UpdateComponentsOfType(RumPi::EComponentType::E_TSL2591);

				//Run the heavier full refresh + UI push once the full-update interval has elapsed on the wall clock.
				if (timeSinceLastFullUpdate.Time() >= fullUpdateIntervalMilliseconds)
				{
					//Restart the clock up front so that even if the refresh below throws, the next attempt is spaced
					//by the full interval - not retried every fast tick, which would re-log the fault ~10x/s.
					timeSinceLastFullUpdate.Start();

					//We started RumPi with Start(false), so the library isn't running its own update loop.
					//Poll the components ourselves here so their readings are refreshed before we read them,
					//otherwise the components are only turned on and GetRawJsonDataFromComponentManager()
					//would keep reporting empty values.
					RumPi::UpdateComponents();

					//Own the event until wxQueueEvent takes it, so a throw from SetString/SetPayload frees it rather
					//than leaking.
					std::unique_ptr<wxThreadEvent> updateEvent(new wxThreadEvent(wxEVT_RUMPI_UPDATE, wxID_ANY));
					updateEvent->SetString(RumPi::GetRawJsonDataFromComponentManager());

					//Piggyback the curated Home-dashboard snapshot on the same event. The payload is deep-copied into
					//the event's wxAny here (and copied out via GetPayload on the UI thread), so this immutable value
					//reaches the UI thread safely - the same mechanism the Logger tab uses for LoggerLineData.
					updateEvent->SetPayload(RumPi::GetSelectedSensorReadings());

					wxQueueEvent(myWxEventHandler, updateEvent.release());
				}
			}
			catch (const std::exception& exception)
			{
				RumPi::AlertManager::GetInstance().Report(RumPi::EAlertLevel::Warning, std::string("RumPiManagerThread::Entry() sensor tick error (continuing): ") + exception.what());
			}
			catch (...)
			{
				RumPi::AlertManager::GetInstance().Report(RumPi::EAlertLevel::Warning, "RumPiManagerThread::Entry() unknown sensor tick error (continuing).");
			}

			//Sleep outside the tick try/catch so pacing holds even on an error tick (no fast spin / log flood on a
			//persistent fault).
			wxMilliSleep(fastTickIntervalMilliseconds);
		}
	}
	catch (const std::exception& exception)
	{
		RumPi::AlertManager::GetInstance().Report(RumPi::EAlertLevel::Alert, std::string("RumPiManagerThread::Entry() fatal startup error, stopping the sensor loop: ") + exception.what());
	}
	catch (...)
	{
		RumPi::AlertManager::GetInstance().Report(RumPi::EAlertLevel::Alert, "RumPiManagerThread::Entry() fatal startup error (unknown), stopping the sensor loop.");
	}

	//We drove the library via Start(false), so we own shutdown. Stop() turns every component off (so relays/motors/
	//LEDs are not left energized), frees the component manager, and drains + closes the logger. Called on this
	//thread - the same one that called Start() - per the library's documented Start(false)/Stop() contract. This
	//runs on every exit path, including after an exception caught above. Wrap it too: Stop() makes GPIO calls, runs
	//component destructors, and closes the logger (none noexcept), so a throw here would otherwise escape Entry()
	//across the wxThread boundary and std::terminate the app on shutdown. Best-effort - we're already exiting.
	try
	{
		RumPi::Stop();
	}
	catch (...)
	{
	}

	return (wxThread::ExitCode)0;
}
