using skyscraper5.Dvb.DataBroadcasting;
using skyscraper5.Mpeg2;
using skyscraper5.Skyscraper.IO;
using skyscraper5.src.InteractionChannel;
using skyscraper8.GSE;
using skyscraper8.GSE.GSE;
using skyscraper8.Skyscraper.IO;
using skyscraper8.Skyscraper.Scraper;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using static skyscraper5.Dvb.Descriptors.ContentIdentifierDescriptor;

namespace skyscraper8.GS.GSE_BFBS
{
	internal class BfbsGseReader : IMisHandler
	{
		private GseLabel lastLabel;
		private int frameNo;
		public GsContextDto Context { get; set; }

		public BfbsGseReader(GsContextDto context)
		{
			this.Context = context;
		}

		private bool CheckAllZeroes(ReadOnlySpan<byte> span)
		{
			for (int i = 0; i < span.Length - 4; i++)
			{
				if (span[i] != 0)
				{
					return false;
				}
			}

			return true;
		}
		
		public void PushFrame(BBHeader bbHeader, ReadOnlySpan<byte> readOnlySpan)
		{
			frameNo++;
			bool validCrc = DvbCrc32.ValidateCrc(readOnlySpan);
			if (!validCrc)
				return;

			if (readOnlySpan[0] == 0 && readOnlySpan[1] == 0 && readOnlySpan[2] == 0 && readOnlySpan[3] == 0)
			{
				if (CheckAllZeroes(readOnlySpan))
				{
					return;
				}
				else
				{
					throw new NotImplementedException("A BBFrame with data offset not at 0 was found. This not supported yet, please share a sample of this.");
				}
			}


			StreamlikeSpan span = new StreamlikeSpan(readOnlySpan);

			while (span.GetAvailableBytes() > 4)
			{
				byte byteA = span.ReadUInt8();
				bool startIndicator = (byteA & 0x80) != 0;
				bool endIndicator = (byteA & 0x40) != 0;
				int labelTypeIndicator = (byteA & 0x30) >> 4;
				if (!startIndicator && !endIndicator && labelTypeIndicator == 0)
				{
					//padding bits, packet has ended.
					return;
				}
				else
				{
					GsePacket gsePacket = new GsePacket(startIndicator, endIndicator, labelTypeIndicator);
					int gseLength = (byteA & 0x0f) << 8;
					gseLength += span.ReadUInt8();

					gsePacket.FragmentId = null;
					if (!startIndicator || !endIndicator)
					{
						gsePacket.FragmentId = span.ReadUInt8();
						gseLength -= 1;
					}

					if ((startIndicator) && !endIndicator)
					{
						gsePacket.TotalLength = span.ReadUInt16BE();
						gseLength -= 2;
					}
					
					if (startIndicator)
					{
						gsePacket.ProtocolType = span.ReadUInt16BE();
						gseLength -= 2;
						if (labelTypeIndicator == 0)
						{
							gsePacket.Label = new _6byteLabel(span.ReadBytes(6));
							gseLength -= 6;
						}
						else if (labelTypeIndicator == 1)
						{
							gsePacket.Label = new _3byteLabel(span.ReadBytes(3));
							gseLength -= 3;
						}
						else if (labelTypeIndicator == 2)
						{
							gsePacket.Label = BroadcastLabel.GetInstance();
						}
						else if (labelTypeIndicator == 3)
						{
							gsePacket.Label = lastLabel;
						}
						lastLabel = gsePacket.Label;
					}

					if (!startIndicator && endIndicator)
						gseLength -= 4;

					if (span.GetAvailableBytes() < gseLength)
					{
						//broken gse packet, invalid length
						return;
					}
					gsePacket.GseDataBytes = span.ReadBytes(gseLength);
					
					if (!startIndicator && endIndicator)
					{
						gsePacket.Crc32 = span.ReadUInt32BE();
					}

					HandleGseFrame(gsePacket);
				}
			}
		}

		private GseFragmentation[] fragmentations;
		private void HandleGseFrame(GsePacket gsePacket)
		{
			if (gsePacket.StartIndicator & gsePacket.EndIndicator)
			{
				HandleAssembledFrame(gsePacket.ProtocolType.Value, gsePacket.GseDataBytes, gsePacket.Label);
				return;
			}

			if (!gsePacket.StartIndicator && gsePacket.EndIndicator)
			{
				if (fragmentations == null)
				{
					//This stream had no fragmentations yet, so we cannot reassemble this packet.
					return;
				}

				if (fragmentations[gsePacket.FragmentId.Value] == null)
				{
					//The previous fragments are missing, so we cannot reassemble this packet.
					return;
				}

				fragmentations[gsePacket.FragmentId.Value].AddFragement(gsePacket);
				if (fragmentations[gsePacket.FragmentId.Value].Validate())
				{
					byte[] gseDataBytes = fragmentations[gsePacket.FragmentId.Value].GetGseDataBytes();
					ushort protocolType = fragmentations[gsePacket.FragmentId.Value].ProtocolType;
					HandleAssembledFrame(protocolType, gseDataBytes, fragmentations[gsePacket.FragmentId.Value].Label);
					fragmentations[gsePacket.FragmentId.Value] = null;
					return;
				}
				else
				{
					throw new NotImplementedException();
				}
			}

			if (gsePacket.StartIndicator && !gsePacket.EndIndicator)
			{
				fragmentations = new GseFragmentation[256];
				fragmentations[gsePacket.FragmentId.Value] = new GseFragmentation(gsePacket);
				return;
			}

			if (!gsePacket.StartIndicator && !gsePacket.EndIndicator)
			{
				if (fragmentations == null)
				{
					//This stream had no fragmentations yet, so we cannot reassemble this packet.
					return;
				}

				if (fragmentations[gsePacket.FragmentId.Value] == null)
				{
					//The previous fragments are missing, so we cannot reassemble this packet.
					return;
				}

				fragmentations[gsePacket.FragmentId.Value].AddFragement(gsePacket);
				return;
			}

			throw new NotImplementedException(
				"A unknown frame type was encountered. Please share a sample of this stream.");
		}

		private void HandleAssembledFrame(ushort protocolType, byte[] buffer, GseLabel label)
		{
			Context.UiJunction?.EnableUiFeature(SkyscraperUiFeature.GseAnalysis);
			Context.UiJunction?.NotifyGsePacket(protocolType, buffer, label);
			switch (protocolType)
			{
				case 0x0081:
					//Network clock reference
					HandleNetworkClockReference(buffer);
					return;
				case 0x0082:
					//Lower Layer Signalling, see en_30154502v010401p.pdf, page 49
					HandleLowerLayerSignalling(buffer, label);
					return;
				case 0x0091:
					//according to https://www.iana.org/assignments/ule-next-headers/ule-next-headers.xhtml it's private
					return;
				case 0x0800:
					Context.IpOutput.OnIpDatagram(0x010e, buffer);
					return;
				default:
					throw new NotImplementedException(protocolType.ToString());
			}
		}

		private GseL2SHandler rcs2;
		private void HandleLowerLayerSignalling(byte[] buffer, GseLabel label)
		{
			if (rcs2 == null)
			{
				rcs2 = new GseL2SHandler(Context);
			}

			rcs2.PushPacket(buffer, label);

		}

		private void HandleNetworkClockReference(byte[] buffer)
		{
			MemoryStream binaryReader = new MemoryStream(buffer);
			uint pcrA = binaryReader.ReadUInt32BE();
			ushort pcrB = binaryReader.ReadUInt16BE();
			ulong pcr_base = ((ulong)pcrA << 1) | ((ulong)pcrB >> 15);
			ulong pcr_ext = (ulong)pcrB & 0x01ff;
			ulong PCR = pcr_base * 300 + pcr_ext;
			ulong seconds = PCR / 27000000;
		}
	}
}
