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

namespace skyscraper8.Ietf.Rfc4236_ULE
{
	internal class UlePacketProcessor : ITsPacketProcessor, IPayloadUnitDecoder
	{
		private static readonly ILog logger = LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType.Name);

		private readonly int _pid;
		private readonly UleEventHandler _eventHandler;

		private bool reassemblyMode;
		private int reassemblyRemainingBytes;
		private MemoryStream reassemblyStream;
		private long offset;

		public UlePacketProcessor(int pid, UleEventHandler eventHandler)
		{
			_pid = pid;
			_eventHandler = eventHandler;
		}

		public void PushPacket(TsPacket packet)
		{
			TsAdaptionField packetAdaptionField = packet.AdaptionField;
			Span<byte> packetPayload = packet.GetPayload();
			if (packetAdaptionField != null || packetPayload == null)
			{
				_eventHandler.OnUleError(_pid, 2);
				return;
			}
			if (packet.PayloadUnitStart)
			{
				PushPusiPacket(packet);
			}
			else
			{
				if (reassemblyMode)
				{
					int availableBytes = Math.Min(184, reassemblyRemainingBytes);
					reassemblyStream.Write(packetPayload.ToArray(), 4, availableBytes);
					reassemblyRemainingBytes -= availableBytes;
					if (reassemblyRemainingBytes == 0)
					{
						reassemblyMode = false;
						reassemblyStream.Position = 0;
						OnReassembledPacket(reassemblyStream);
						reassemblyStream = null;
					}
				}
				else
				{
					//Es gibt keine neue Payload Unit, und wir bauen uns auch keine zusammen, also diese wegwerfen.
					return;
				}
			}

			offset += 188;
		}

		private void PushPusiPacket(TsPacket packet)
		{
			bool multipleSndusInPacket = false;

			MemoryStream payload = new MemoryStream(packet.GetRawPacket(), false);
			payload.Position += 4;
			byte nextPacketOffset = payload.ReadUInt8();
			if (!reassemblyMode)
				payload.Position += nextPacketOffset;

			while (payload.GetAvailableBytes() > 0)
			{
				if (reassemblyMode)
				{
					int availableBytes = Math.Min((int)payload.GetAvailableBytes(), reassemblyRemainingBytes);
					byte[] stepBuffer = new byte[availableBytes];
					int sucessfullyCopiedBytes = payload.Read(stepBuffer, 0, availableBytes);
					if (sucessfullyCopiedBytes != availableBytes)
						throw new UleException("Byte copy operation failed during reassembly.");
					reassemblyStream.Write(stepBuffer, 0, sucessfullyCopiedBytes);
					reassemblyRemainingBytes -= sucessfullyCopiedBytes;
					if (reassemblyRemainingBytes == 0)
					{
						reassemblyMode = false;
						reassemblyStream.Position = 0;
						OnReassembledPacket(reassemblyStream);
						reassemblyStream = null;
						multipleSndusInPacket = true;
					}
				}
				else
				{
					if (payload.GetAvailableBytes() == 1)
					{
						/*"If only one byte remains unprocessed in the TS Packet payload
						after completion of the Current SNDU, the Receiver MUST discard this
						final byte of TS Packet payload."*/
						break;
					}

					byte lengthByteA = payload.ReadUInt8();
					bool destinationAddressAbsent = (lengthByteA & 0x80) != 0;

					byte lengthByteB = payload.ReadUInt8();

					int length = lengthByteA & 0x7f;
					length <<= 8;
					length += lengthByteB;
					length += 2;

					if (length == 0x8001)
					{
						break;
					}
					
					reassemblyMode = true;
					reassemblyRemainingBytes = length;
					reassemblyStream = new MemoryStream();
					reassemblyStream.WriteUInt8(lengthByteA);
					reassemblyStream.WriteUInt8(lengthByteB);
				}
			}
		}

		private void OnReassembledPacket(MemoryStream ms)
		{
			bool crc = DvbCrc32.ValidateCrc(ms, 0, (int)ms.Length);
			if (!crc)
			{
				_eventHandler.OnUleError(_pid, 1);
				return;
			}

			byte lengthByteA = ms.ReadUInt8();
			bool destinationAddressAbsent = (lengthByteA & 0x80) != 0;
			byte lengthBytesB = ms.ReadUInt8();

			ushort typeField = ms.ReadUInt16BE();

			PhysicalAddress destAddress = null;
			if (!destinationAddressAbsent)
				destAddress = new PhysicalAddress(ms.ReadBytes(6));

			long packetSize = ms.GetAvailableBytes() - 4;
			byte[] pdu = ms.ReadBytes(packetSize);
			uint crc32 = ms.ReadUInt32BE();

			switch (typeField)
			{
				case 0:
					OnTestSndu(pdu);
					break;
				case 1:
					OnBridgedFrame(pdu);
					break;
				case 0x0100:
					OnExtensionPadding(pdu);
					break;
				case 0x0800:
					OnIpv4Payload(pdu);
					break;
				case 0x86dd:
					OnIpv6Payload(pdu);
					break;
				default:
					throw new NotImplementedException(String.Format("ULE Type 0x{0:X4}", typeField));
			}
		}

		private void OnIpv6Payload(byte[] pdu)
		{
			logger.Fatal("IPv6 Payload not yet implemented!");
		}

		private void OnIpv4Payload(byte[] pdu)
		{
			logger.Fatal("IPv4 Payload not yet implemented!");
		}

		private void OnExtensionPadding(byte[] pdu)
		{
			logger.Fatal("Extension Padding not yet implemented.");
		}

		private void OnTestSndu(byte[] pdu)
		{
			logger.Fatal("Test SNDU not yet implemented!");
		}

		private void OnBridgedFrame(byte[] pdu)
		{
			MemoryStream ms = new MemoryStream(pdu, false);
			PhysicalAddress destination = new PhysicalAddress(ms.ReadBytes(6));
			PhysicalAddress source = new PhysicalAddress(ms.ReadBytes(6));
			ushort etherType = ms.ReadUInt16BE();
			byte[] contents = ms.ReadBytes(ms.GetAvailableBytes());
			_eventHandler.OnEthernetFrame(_pid, destination, source, etherType, contents);
		}

		public void PacketLoss()
		{
			reassemblyMode = false;
			reassemblyRemainingBytes = 0;
			reassemblyStream = null;
		}


	}
}
