using log4net;
using skyscraper5.Mpeg2;
using skyscraper5.Skyscraper;
using skyscraper5.Skyscraper.IO;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.NetworkInformation;
using System.Net.Sockets;
using System.Runtime.Serialization;
using System.Text;
using System.Threading.Tasks;

namespace skyscraper8.Skyscraper
{
	internal class BbframeDeencapsulator
	{
		private bool interruptedGseHem;
		private MemoryStream interruptedGseHemBuffer;
		private bool interruptedGseHemWantsCrc32;
		public IGsEventHandler MpeEventHandler { get; set; }

        private static readonly ILog logger = LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType.Name);

        public void PushPacket(byte[] bbframe)
		{
			MemoryStream ms = new MemoryStream(bbframe, false);
			//BBHeader
			byte matype1 = ms.ReadUInt8();
			int tsGsField = (matype1 & 0xc0) >> 6;
			bool sisMisField = (matype1 & 0x20) != 0;
			bool ccmAcmField = (matype1 & 0x10) != 0;
			bool issyi = (matype1 & 0x08) != 0;
			bool npd = (matype1 & 0x04) != 0;
			int ro = (matype1 & 0x03);

			byte matype2 = ms.ReadUInt8();

			ushort userPacketLength = ms.ReadUInt16BE();
			ushort dataFieldLength = ms.ReadUInt16BE();
			byte sync = ms.ReadUInt8();
			ushort syncd = ms.ReadUInt16BE();
			byte crc8 = ms.ReadUInt8();

			switch (tsGsField)
			{
				case 2:
					int bytes = dataFieldLength / 8;
					if (ms.GetAvailableBytes() < bytes)
						return;
					HandleGse(ms);
					break;
				default:    //0 = generic packetized, 1 = generic continouus, 2 = gse, 3 = ts
					logger.Warn(String.Format("Unsupported: TS/GS field says 0x{0:X2}", tsGsField));
					break;
			}
		}

		private bool gseNeedMore;
		private MemoryStream gseAssembler;
		public int PID;

		private void HandleGse(MemoryStream ms)
		{
			ms.Position = 10;
			byte syncByte = ms.ReadUInt8();
			ms.Position = 10;

			long availableBytes = ms.GetAvailableBytes();
			byte[] readBytes = ms.ReadBytes(availableBytes);
			
			GseAssemblyState assemblyState = ValidateGse(gseAssembler);
			if (assemblyState == GseAssemblyState.NOT_YET_BEGUN)
			{
				if ((syncByte & 0xc0) == 0xc0)
				{
					//Aktuell ist der GSE Assembler nicht beschäftigt, aber wir haben ein Sync-Byte, also kann er loslegen.
					gseAssembler = new MemoryStream();
					gseAssembler.Write(readBytes, 0, readBytes.Length);
					return;
				}
				else
				{
					//Aktuell ist der GSE Assembler nicht beschäftigt, und wir haben kein gültiges Sync-Byte, also weg damit.
					//Console.WriteLine("gse not in sync yet. sync byte = {0}", syncByte);
					return;
				}
			}
			else if (assemblyState == GseAssemblyState.NEED_MORE_DATA)
			{
				gseAssembler.Write(readBytes, 0, readBytes.Length);
				return;
			}
			else if (assemblyState == GseAssemblyState.VALID)
			{
				gseAssembler.Position = 0;
				AssembleGse(gseAssembler);
				//Console.WriteLine("assembled {0} next sync byte is {1}", sucessfulAssembles++,syncByte);
				if (sucessfulAssembles == 181)
				{

				}
				gseAssembler = null;
				HandleGse(ms);
				return;
			}
			else if (assemblyState == GseAssemblyState.BROKEN_SUSPECT_ETHERTYPE)
			{
				gseAssembler = null;
				HandleGse(ms);
				return;
			}
			else if (assemblyState == GseAssemblyState.BROKEN_NEGATIVE_LENGTH)
			{
				gseAssembler = null;
				HandleGse(ms);
				return;
			}
			else
			{
				throw new NotImplementedException(String.Format("Unknown GSE assembler state: {0}, sync byte 0x{1}", assemblyState, syncByte));
			}
		}

		private long sucessfulAssembles;
		enum GseAssemblyState
		{
			NOT_YET_BEGUN,
			VALID,
			BROKEN_NEGATIVE_LENGTH,
			NEED_MORE_DATA,
			BROKEN_SUSPECT_LENGTH,
			BROKEN_SUSPECT_ETHERTYPE
		}

		private GseAssemblyState ValidateGse(MemoryStream ms)
		{
			if (ms == null)
				return GseAssemblyState.NOT_YET_BEGUN;

			long backupPosition = ms.Position;
			ms.Position = 0;
			while (ms.GetAvailableBytes() > 2)
			{
				//GSE-Header
				byte byteA = ms.ReadUInt8();
				bool startIndicator = (byteA & 0x80) != 0;
				bool endIndicator = (byteA & 0x40) != 0;
				int labelIndicator = (byteA & 0x30) >> 4;
				if (!startIndicator && !endIndicator && labelIndicator == 0)
				{
					//end of base band frame
					ms.Position = backupPosition;
					return GseAssemblyState.VALID;
				}
				else
				{
					byte byteB = ms.ReadUInt8();
					int gseLength = byteA & 0x0f;
					gseLength <<= 8;
					gseLength += byteB;
					//Console.WriteLine("GSE Length: {0}", gseLength);
					if (gseLength > 9000)
					{
						ms.Position = backupPosition;
						return GseAssemblyState.BROKEN_SUSPECT_LENGTH;
					}

					if (!startIndicator || !endIndicator)
					{
						byte fragId = ms.ReadUInt8();
						gseLength--;
					}

					if (startIndicator && !endIndicator)
					{
						ushort totalLength = ms.ReadUInt16BE();
						gseLength -= 2;
					}

					if (startIndicator)
					{
						if (2 > ms.GetAvailableBytes())
						{
							ms.Position = backupPosition;
							return GseAssemblyState.NEED_MORE_DATA;
						}
						ushort protocolType = ms.ReadUInt16BE();
						if (!ValidateEthertype(protocolType))
						{
							ms.Position = backupPosition;
							return GseAssemblyState.BROKEN_SUSPECT_ETHERTYPE;
						}
						gseLength -= 2;
						if (labelIndicator == 0)
						{
							if (6 > ms.GetAvailableBytes())
							{
								ms.Position = backupPosition;
								return GseAssemblyState.NEED_MORE_DATA;
							}
							PhysicalAddress sixByteLabel = new PhysicalAddress(ms.ReadBytes(6));
							gseLength -= 6;
						}
						else if (labelIndicator == 1)
						{
							byte[] threeByteLabel = ms.ReadBytes(3);
							gseLength -= 3;
						}
					}

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

					int startCrc32 = (int)ms.Position;

					if (gseLength < 0)
					{
						ms.Position = backupPosition;
						return GseAssemblyState.BROKEN_NEGATIVE_LENGTH;
					}

					if (gseLength > ms.GetAvailableBytes())
					{
						ms.Position = backupPosition;
						return GseAssemblyState.NEED_MORE_DATA;
					}

					ms.Position += gseLength;
					if (!startIndicator && endIndicator)
					{
						uint crc32 = ms.ReadUInt32BE();
						int endCrc32 = (int)ms.Position;
						DvbCrc32.ValidateCrc(ms, startCrc32, endCrc32);
					}
				}
			}

			ms.Position = backupPosition;
			return GseAssemblyState.VALID;
		}

		private bool ValidateEthertype(ushort ethertype)
		{
			if (ethertype == 0x0800)
				return true;			//IPv4
			if (ethertype == 0x86DD)
				return true;			//IPv6
			else
				return false;			//There are many more valid values, but since we don't speak anything aside from IPv4/IPv6, we discard it.
		}
		private void AssembleGse(MemoryStream ms)
		{
			ushort protocolType = 0;
			while (ms.GetAvailableBytes() > 2)
			{
				//GSE-Header
				byte byteA = ms.ReadUInt8();
				bool startIndicator = (byteA & 0x80) != 0;
				bool endIndicator = (byteA & 0x40) != 0;
				int labelIndicator = (byteA & 0x30) >> 4;
				if (!startIndicator && !endIndicator && labelIndicator == 0)
				{
					//end of base band frame
					return;
				}
				else
				{
					byte byteB = ms.ReadUInt8();
					int gseLength = byteA & 0x0f;
					gseLength <<= 8;
					gseLength += byteB;

					if (!startIndicator || !endIndicator)
					{
						byte fragId = ms.ReadUInt8();
						gseLength--;
					}

					if (startIndicator && !endIndicator)
					{
						ushort totalLength = ms.ReadUInt16BE();
						gseLength -= 2;
					}

					if (startIndicator)
					{
						protocolType = ms.ReadUInt16BE();
						gseLength -= 2;
						if (labelIndicator == 0)
						{
							PhysicalAddress sixByteLabel = new PhysicalAddress(ms.ReadBytes(6));
							gseLength -= 6;
						}
						else if (labelIndicator == 1)
						{
							byte[] threeByteLabel = ms.ReadBytes(3);
							gseLength -= 3;
						}
					}

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

					int startCrc32 = (int)ms.Position;
					
					byte[] payload = ms.ReadBytes(gseLength);
					if (!startIndicator && endIndicator)
					{
						uint crc32 = ms.ReadUInt32BE();
						int endCrc32 = (int)ms.Position;
						DvbCrc32.ValidateCrc(ms, startCrc32, endCrc32);
					}

					switch (protocolType)
					{
						case 0x0800:	//IPv4
							MpeEventHandler.OnIpDatagram(PID, payload);
							break;
						default:
							//throw new NotImplementedException(String.Format("Unknown GSE Protocol ID 0x{0:X4}", protocolType));
							break;
					}
				}
			}
		}


	}
}
