using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using NmeaParser;
using NmeaParser.Messages;
using skyscraper5.Skyscraper.Gps;

namespace skyscraper5.NmeaSharpWrapper
{
	abstract class NmeaSharpWrapper : IGpsReceiver
	{
		public bool ProvidesAdditionalNmeaData => additionalNmeaData != null;
		

		public bool HasLock => seenLock && seenLonLat;

		private bool seenLock, seenLonLat;
		private GpsCoordinate receivedCoordinate;
		public GpsCoordinate Coordinate => receivedCoordinate;
		public AdditionalNmeaData AdditionalNmeaData => additionalNmeaData;


		public void Stop()
		{
			CancelRequest = true;
		}

		public void Start()
		{
			_thread = new Thread(ProcessorThread);
			_thread.Priority = ThreadPriority.Lowest;
			_thread.Name = String.Format("GPS Receiver: {0}", this.GetType().Name);
			_thread.Start();
		}

		protected Thread _thread;
		protected abstract void ProcessorThread();
		protected bool CancelRequest { get; set; }
		private AdditionalNmeaData additionalNmeaData;

		protected void HandleNmeaMessage(object? sender, NmeaMessageReceivedEventArgs e)
		{
			HandleNmeaMessage(e.Message);
		}

		protected void HandleNmeaMessage(string s)
		{
			NmeaMessage nmeaMessage = NmeaMessage.Parse(s);
			HandleNmeaMessage(nmeaMessage);
		}

		protected void HandleNmeaMessage(NmeaMessage message)
		{
			string messageMessageType = message.MessageType;
			switch (messageMessageType)
			{
				case "GPGLL":
					if (additionalNmeaData == null)
						additionalNmeaData = new AdditionalNmeaData();
					Gll gll = (Gll)message;
					additionalNmeaData.ModeIndicator = gll.ModeIndicator.ToString();
					additionalNmeaData.DataActive = gll.DataActive;
					additionalNmeaData.FixTime = gll.FixTime;
					receivedCoordinate = new GpsCoordinate((float)gll.Latitude, (float)gll.Longitude);
					seenLonLat = true;
					break;
				case "GPVTG":
					Vtg vtg = (Vtg)message;
					if (additionalNmeaData == null)
						additionalNmeaData = new AdditionalNmeaData();
					additionalNmeaData.CourseMagnetic = vtg.CourseMagnetic;
					additionalNmeaData.CourseTrue = vtg.CourseTrue;
					additionalNmeaData.SpeedKnots = vtg.SpeedKnots;
					additionalNmeaData.SpeedKph = vtg.SpeedKph;
					break;
				case "GPZDA":
					Zda zda = (Zda)message;
					if (additionalNmeaData == null)
						additionalNmeaData = new AdditionalNmeaData();
					additionalNmeaData.FixDateTime = zda.FixDateTime;
					break;
				case "GPGGA":
					if (additionalNmeaData == null)
						additionalNmeaData = new AdditionalNmeaData();
					Gga gga = (Gga)message;
					additionalNmeaData.Quality = gga.Quality.ToString();
					additionalNmeaData.Altitude = gga.Altitude;
					additionalNmeaData.AltitudeUnits = gga.AltitudeUnits;
					additionalNmeaData.DpgsStationId = gga.DgpsStationId;
					additionalNmeaData.FixTime = gga.FixTime;
					additionalNmeaData.GeoidalSeparation = gga.GeoidalSeparation;
					additionalNmeaData.GeoidalSeparationUnits = gga.GeoidalSeparationUnits;
					additionalNmeaData.Hdop = gga.Hdop;
					additionalNmeaData.NumberOfSatellites = gga.NumberOfSatellites;
					additionalNmeaData.TimeSinceLastDgpsUpdate = gga.TimeSinceLastDgpsUpdate;
					if (gga.Quality == Gga.FixQuality.GpsFix || gga.Quality == Gga.FixQuality.DgpsFix)
					{
						receivedCoordinate = new GpsCoordinate((float)gga.Latitude, (float)gga.Longitude);
						seenLonLat = true;
						seenLock = true;
					}
					break;
				case "GPGSA":
					if (additionalNmeaData == null)
						additionalNmeaData = new AdditionalNmeaData();
					Gsa gsa = (Gsa)message;
					additionalNmeaData.Hdop = gsa.Hdop;
					additionalNmeaData.Fix = gsa.Fix.ToString();
					additionalNmeaData.Mode = gsa.Mode.ToString();
					additionalNmeaData.Pdop = gsa.Pdop;
					additionalNmeaData.SatelliteIDs = gsa.SatelliteIDs;
					additionalNmeaData.Vdop = gsa.Vdop;
					if (gsa.Fix == Gsa.FixType.Fix2D || gsa.Fix == Gsa.FixType.Fix3D)
						seenLock = true;
					break;
				case "GPRMC":
					if (additionalNmeaData == null)
						additionalNmeaData = new AdditionalNmeaData();
					Rmc rmc = (Rmc)message;
					receivedCoordinate = new GpsCoordinate((float)rmc.Latitude, (float)rmc.Longitude);
					seenLonLat = true;
					additionalNmeaData.Active = rmc.Active;
					additionalNmeaData.CourseTrue = rmc.Course;
					additionalNmeaData.FixTime = rmc.FixTime.TimeOfDay;
					additionalNmeaData.MagneticVariation = rmc.MagneticVariation;
					additionalNmeaData.SpeedKnots = rmc.Speed;
					break;
				case "GPGSV":
					if (additionalNmeaData == null)
						additionalNmeaData = new AdditionalNmeaData();
					Gsv gsv = (Gsv)message;
					additionalNmeaData.GnssSignalId = gsv.GnssSignalId;
					additionalNmeaData.SatellitesInView = gsv.SatellitesInView;
					foreach (SatelliteVehicle vehicle in gsv.SVs)
						additionalNmeaData.AddSatellite(vehicle.Id, vehicle.GnssSignalId, vehicle.Azimuth,
							vehicle.Elevation, vehicle.SignalToNoiseRatio, vehicle.System.ToString(),
							vehicle.TalkerId.ToString());
					break;
				case "GPGBS":
					if (additionalNmeaData == null)
						additionalNmeaData = new AdditionalNmeaData();
					Gbs gbs = (Gbs)message;
					additionalNmeaData.LatitudeError = gbs.LatitudeError;
					additionalNmeaData.LongitudeError = gbs.LongitudeError;
					additionalNmeaData.AltitudeError = gbs.AltitudeError;
					additionalNmeaData.SatelliteId = gbs.SatelliteId;
					additionalNmeaData.MissedDetectionProbability = gbs.MissedDetectionProbability;
					additionalNmeaData.BiasEstimate = gbs.BiasEstimate;
					additionalNmeaData.StandardDeviation = gbs.StandardDeviation;
					break;
				case "GSGPG":
				case "GPG":
				case "GPTXT":
					//does not seem to contain anything?
					break;
				default:
					throw new NotImplementedException(messageMessageType);
			}
		}
	}
}
