using skyscraper5.Ietf.Rfc768;
using skyscraper5.Ietf.Rfc971;
using skyscraper8.DvbNip;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Text;
using System.Threading.Tasks;

namespace skyscraper8.Ietf.FLUTE
{
	internal class FluteListener
	{
		
		internal void AddPlay(IPAddress iPAddress, int port, ulong tsi, ulong toi)
		{
			if (knownFlutePlays == null)
				knownFlutePlays = new List<FlutePlay>();

			FlutePlay? flutePlay = knownFlutePlays.Find(x => x.IPAddress.Equals(iPAddress) &&
				x.Port == port &&
				x.TSI == tsi &&
				x.TOI == toi);
			if (flutePlay != null)
				return;

			FlutePlay play = new FlutePlay(iPAddress, port, tsi, toi);
			knownFlutePlays.Add(play);
		}

		internal void OnFlutePacket(IPAddress destinationAddress, ushort destinationPort, byte[] payload)
		{
			if (knownFlutePlays == null)
				return;

			if (eventHandler.IpFilterEnabled())
			{
				FlutePlay? ipFound = knownFlutePlays.Find(x => x.IPAddress.Equals(destinationAddress) && x.Port == destinationPort);
				if (ipFound == null)
					return;
			}
			

			LctFrame lctFrame = new LctFrame(payload);
			eventHandler.InformToi(destinationAddress, destinationPort, lctFrame.LctHeader.TransportObjectIdentifier, lctFrame.LctHeader.TransportSessionIdentifier);
			if (lctFrame.LctHeader.NipActualCarrierInformation != null)
			{
				eventHandler.OnNipCarrierInformation(lctFrame.LctHeader.NipActualCarrierInformation);
				return;
			}

			FlutePlay? flutePlay = knownFlutePlays.Find(x => x.IPAddress.Equals(destinationAddress) && 
			x.Port == destinationPort && 
			x.TSI == lctFrame.LctHeader.TransportSessionIdentifier && 
			x.TOI == lctFrame.LctHeader.TransportObjectIdentifier);

			if (flutePlay == null)
				return;

			flutePlay.OnPacket(lctFrame);

			if (flutePlay.IsComplete())
			{
				eventHandler.OnCompletePlay(flutePlay);
			}
		}

		internal void RemoveAllPlays()
		{
			if (knownFlutePlays == null)
				return;

			knownFlutePlays.Clear();
		}

		internal void SetFileAssociation(IPAddress iPAddress, int port, ulong tSI, string tOI, FileType filetype)
		{
			ulong toiNumeric = ulong.Parse(tOI);

			FlutePlay? flutePlay = knownFlutePlays.Find(x => x.IPAddress.Equals(iPAddress) &&
			x.Port == port &&
			x.TSI == tSI &&
			x.TOI == toiNumeric);
			if (flutePlay != null)
			{
				flutePlay.FileAssociation = filetype;
			}
			else
			{
				AddPlay(iPAddress, port, tSI, toiNumeric);
				SetFileAssociation(iPAddress, port, tSI, tOI, filetype);
			}
		}

		private List<FlutePlay> knownFlutePlays;
		private IFluteEventHandler eventHandler;

		public FluteListener(IFluteEventHandler dvbNipReceiver)
		{
			this.eventHandler = dvbNipReceiver;
		}

		public class FlutePlay
		{
			public FlutePlay(IPAddress iPAddress, int port, ulong tsi, ulong toi)
			{
				IPAddress = iPAddress;
				Port = port;
				TSI = tsi;
				TOI = toi;
			}

			public override string ToString()
			{
				if (ignore)
				{
					return "<disabled>";
				}
				return String.Format("IP={0}, Port={1}, TOI={2}, TSI={3}, Name={4}", IPAddress, Port, TOI, TSI, FileAssociation?.ContentLocation);
			}

			public IPAddress IPAddress { get; private set; }
			public ulong TSI { get; private set; }
			public ulong TOI { get; private set; }
			public int Port { get; }
			public FileType FileAssociation { get; internal set; }

			private int neededEsi, neededSbn;
			private ulong nextX, maxX;
			private ulong encodingSymbolLength;
			private ulong transferLength;
			private byte[][] buffers;
			private bool ignore;

			internal void OnPacket(LctFrame lctFrame)
			{
				if (ignore == true)
					return;

				if (neededEsi == 0 && neededSbn == 0 && lctFrame.FecHeader.EncodingSymbolId == 0 && lctFrame.FecHeader.SourceBlockNumber == 0)
				{
					nextX = 0;
					encodingSymbolLength = lctFrame.LctHeader.FecObjectTransmissionInformation.EncodingSymbolLength;
					transferLength = lctFrame.LctHeader.FecObjectTransmissionInformation.TransferLength;
					maxX = (transferLength / encodingSymbolLength) + 1;
					buffers = new byte[maxX][];
					buffers[nextX] = lctFrame.Payload;
					neededEsi++;
				}
				else if (neededEsi != 0 || neededSbn != 0)
				{
					if (IsComplete())
						return;

					nextX++;
					buffers[nextX] = lctFrame.Payload;
				}
			}

			internal bool IsComplete()
			{
				if (ignore == true)
					return false;

				if (buffers == null)
					return false;

				ulong currentSize = 0;
				for (int i = 0; i < buffers.Length; i++)
				{
					if (buffers[i] == null)
						continue;
					currentSize += (ulong)buffers[i].Length;
				}
				return currentSize >= transferLength;
			}

			public _2DMemoryStream GetStream()
			{
				return new _2DMemoryStream(buffers);
			}


			public void WriteToFile(string filename)
			{
				FileInfo fi = new FileInfo(filename);
				if (fi.Exists)
					fi.Delete();
				FileStream fileStream = fi.OpenWrite();
				_2DMemoryStream _2DMemoryStream = GetStream();
				_2DMemoryStream.CopyTo(fileStream);
				fileStream.Flush();
				fileStream.Close();
				fileStream.Dispose();
			}

			internal void SetIgnore(bool v)
			{
				if (v)
				{
					for (int i = 0; i < buffers.Length; i++)
					{
						buffers[i] = null;
					}
					buffers = null;
					ignore = true;
				}
				else
				{
					neededEsi = 0;
					neededSbn = 0;
					nextX = 0;
					buffers = new byte[maxX][];
					ignore = false;
				}
			}

			public bool Validate()
			{
				if (buffers.Length == 1)
				{
					if (buffers[0] != null)
						return true;
					else
						return false;
				}
				int lengthA = buffers[0].Length;
				for (int i = 1; i < buffers.Length; i++) 
				{
					if (buffers[i].Length == lengthA)
						continue;
					else if (buffers[i].Length < lengthA)
						lengthA = buffers[i].Length;
					else if (buffers[i].Length > lengthA)
						return false;
					else
						throw new NotImplementedException("Failed to validate a FLUTE play");
				}
				return true;
			}
		}


	}
}
