
// ===================================================================================================
// SatGen 4 API Code Sample
// ===================================================================================================
// A starting point for integrating the SatGen Scenario API into your own application. The code below
// demonstrates the minimal steps required to configure and run a GNSS simulation: the active calls
// produce a static-point trajectory streamed in real time to a LabSat 4, but every section can be
// swapped for one of the alternatives described in the comments.
//
// For the full list of methods, parameters, and supported values, see API-Documentation.html
// (generated alongside this sample on build).
//
// Prerequisites:
//   - SatGen 4 must be installed (the API DLLs are loaded from the SatGen 4 install directory)
//   - A valid SatGen 4 licence must be available (hardware dongle)
//   - For real-time output only: a LabSat Real-Time or LabSat 4 device connected and accessible
//     (file output does not require any LabSat hardware)
//
// Startup parameters (optional):
//   SatGen4APICodeSamples.exe [LabSat4IPAddress] [SimulationDateTime]
//   Example: SatGen4APICodeSamples.exe 192.168.1.100 "01 Jan 2026 12:00:00"
// ===================================================================================================

using SatGen.ScenarioAPI;

internal static class Program
{
	private static void Main(string[] args)
	{
		// -- CONFIGURATION -----------------------------------------------------------------------
		// Default LabSat 4 IP address - change this to match your device, or pass as a startup parameter.
		var labSatIPAddress = "192.168.1.100";

		// Simulation start date/time in UTC. Satellite constellation positions are calculated from this
		// time, so use a recent date for realistic satellite geometry. Sub-second precision is discarded.
		var simulationDateTime = new DateTime(2026, 01, 01, 12, 00, 00);

		// Allow overriding configuration via startup parameters
		if (args.Length >= 1)
		{
			labSatIPAddress = args[0];
		}

		if (args.Length >= 2 && DateTime.TryParse(args[1], out var startupParamSimulationDateTime))
		{
			simulationDateTime = startupParamSimulationDateTime;
		}

		try
		{
			// -- SCENARIO CREATION ---------------------------------------------------------------
			// The Scenario is the root object representing a simulation run. Its constructor takes
			// the simulation start time and an optional attenuationDB value, which adds artificial
			// noise to the output signal (equivalent to the 'Attenuation' box on the Output tab in
			// SatGen 4; typical values range from -10 to 0 dB).
			using var scenario = new Scenario(simulationDateTime, attenuationDB: -3);

			// -- ALMANACS (optional) -------------------------------------------------------------
			// By default the API downloads the latest almanac for each constellation from the
			// Racelogic almanac server. If you need to reproduce a known scenario or work offline,
			// a local almanac file can be supplied per constellation via ManualAlmanacFile().

			// -- SIGNALS -------------------------------------------------------------------------
			// Defines which GNSS signals are simulated. Each enabled signal adds CPU load, so only
			// enable what your receiver under test requires. See API-Documentation.html (SignalType)
			// for the full list of supported signals across all constellations.
			var signals = new[]
			{
				SignalType.GpsL1CA,
				SignalType.GpsL2C,
            };

			// -- DYNAMICS (optional) -------------------------------------------------------------
			// The dynamics profile constrains how the simulated vehicle accelerates, decelerates,
			// and corners. It only applies to route-style trajectories (waypoints, KML, etc.);
			// imported trajectories (NMEA, VBOX, CSV) carry their own motion data.
			//
			// SetDynamics() selects a built-in preset (Car, RaceCar, Train, Pedestrian, ...) and
			// SetCustomDynamics() lets you specify exact velocity, acceleration, and jerk limits.

			// -- TRAJECTORY ----------------------------------------------------------------------
			// Defines where and how the simulated receiver moves. Many trajectory types are
			// available - static point, route waypoints, KML, NMEA, VBOX, CSV, and full
			// .sgen scenarios - each exposed as a TrajectoryFrom... method. See API-Documentation.html
			// for the full list and per-method options.
			scenario.TrajectoryFromStaticPoint(
				latitudeDecDegrees: 51.9831552,
				longitudeDecDegrees: -0.999424,
				altitudeMetres: 100,
				durationInSeconds: 600);

            // -- DEVICE DISCOVERY (optional) -----------------------------------------------------
            // FindAvailableLabSat4RealTimeDevices() returns the LabSat 4 devices discovered on the
            // network, including each device's IP address, serial number, and ready state. Useful
            // when the IP address is not known in advance.

            // -- OUTPUT --------------------------------------------------------------------------
            // Configures where the simulated signals are sent. Real-time targets (LabSat 1/2/3,
            // LabSat 4, LabSat 4 RT+) and file outputs (LS2 / LS3 / LS3W / LS4) are all supported,
            // each exposed as a SetOutputTo... method. See API-Documentation.html for the full list.
            //
            // Quantization controls signal fidelity: higher bit depths produce more accurate signals
            // but increase the data rate, which may limit the number of simultaneous signals.
            //
            // SatCountLimitMode controls CPU load management for real-time outputs. Automatic
            // (recommended) starts with the maximum number of satellites and reduces if the CPU is
            // overloaded; the alternatives let you simulate all visible satellites or manage them
            // manually via SetSatelliteEnabled().
            scenario.SetOutputToLabSat4Realtime(
                labSatIPAddress,
                RealTimeOutputMode.UserTime,
                signals,
                Quantization.TwelveBit,
                new LabSat4RealtimeOutputOptions { SatCountLimitMode = SatCountLimitMode.Automatic });

            // -- TIMELINE EVENTS (optional) ------------------------------------------------------
            // Schedule satellite changes to be applied automatically as the simulation progresses.
            // Each AddSatelliteTimelineChange() call queues a change at a specified elapsed time;
            // when the simulation reaches that time, the satellite's enabled state and attenuation
            // are updated. Useful for repeatable test cases such as driving through a tunnel,
            // simulating a satellite outage, or staging a gradual signal degradation.
            // ClearSatelliteTimeline() removes every previously scheduled change.
            //
            // IMPORTANT: schedule changes BEFORE calling StartSimulation. The timeline is snapshotted
            // when the simulation begins; subsequent additions will not take effect on the running run.

            // -- INSPECT THE CONFIGURATION (optional) --------------------------------------------
            // Before starting the simulation, you can inspect what the engine has decided to do:
            // GetSimulationSummary() reports the start time, trajectory, output, and signals;
            // GetChannelPlan() reports the RF channels, sample rate, bandwidth, and quantization.

            // -- SIMULATION ----------------------------------------------------------------------
            // Starts the simulation. The API will print the configuration summary, almanac status,
            // and channel plan to the console before the simulation begins.
            scenario.StartSimulation();

			// Track the simulation start time so we can display the total duration when it finishes
			var simulationStartTime = DateTime.Now;

			// MonitorSimulationProgress blocks until the simulation ends (either the trajectory
			// duration elapses, or EndSimulation() is called from another thread). The optional
			// callback runs on each interval tick - useful for printing progress or reacting to
			// simulation state.
			scenario.MonitorSimulationProgress(TimeSpan.FromSeconds(1),
				() =>
				{
					if (scenario.IsRunning)
					{
						// Format elapsed time and build a progress bar for visual feedback
						var elapsed = TimeSpan.FromSeconds(scenario.SimulationSecondsFromStart);
						var percent = scenario.SimulationPercentComplete;
						int barWidth = 20;
						int filled = (int)(percent / 100 * barWidth);
						string bar = new string('#', filled) + new string('-', barWidth - filled);

						Console.Write($"\r  [{bar}] {percent,5:F1}%   Elapsed: {elapsed:hh\\:mm\\:ss}   Underruns: {scenario.BufferUnderrunCount}   ");

						// -- RUNTIME CONTROL (optional) -----------------------------------------
						// While a real-time simulation is running, several methods can adjust the
						// output and constellation on the fly:
						//   - GetSatellitesInRealTimeSimulation(), SetSatelliteEnabled(), and
						//     SetSatelliteSignalGainDB() inspect and control individual satellites
						//     (useful for stress-testing receiver behaviour under degraded
						//     constellations).
						//   - SetLabSat4ChannelAttenuation() adjusts the RF output level of an
						//     entire LabSat 4 channel - the API equivalent of the LabSat 4 front-
						//     panel up/down buttons.
						//
						// IMPORTANT: this callback fires on every interval tick. Calling any of
						// these methods directly here will re-apply the change on every tick,
						// which is rarely what you want. Guard one-shot changes with a flag or an
						// elapsed-time check - for example, apply the change the first time
						// scenario.SimulationSecondsFromStart crosses a threshold.
						//
						// IMPORTANT: these calls are only valid for real-time outputs. On a file
						// output they throw.
						//
						// For changes that should happen at fixed times, the TIMELINE EVENTS
						// section above is usually a better fit - the engine handles the timing.
					}
					else if (scenario.IsReady)
					{
						Console.Write("\r  Waiting for trajectory data...                                          ");
					}
				});

			// Print the final simulation summary
			var totalDuration = DateTime.Now - simulationStartTime;
			Console.WriteLine();
			Console.WriteLine();
			Console.WriteLine($"  Simulation complete. Total time: {totalDuration:hh\\:mm\\:ss}");
			scenario.EndSimulation();
		}
		catch (Exception ex)
		{
			// ex.Message is fully formatted by the API for every exception type it throws, including
			// the AggregateException used to report multiple script-parser errors - no special handling
			// is needed here. Advanced consumers can catch a more specific type (e.g. AggregateException)
			// and inspect InnerExceptions if they need to react to individual errors programmatically.
			Console.WriteLine();
			Console.WriteLine($"  ERROR: {ex.Message}");
		}
	}
}
