using skyscraper8.DvbNip;
using System;
using System.Collections.Generic;
using System.IO.Compression;
using System.Linq;
using System.Linq.Expressions;
using System.Net;
using System.Runtime.Serialization;
using System.Security.Cryptography.X509Certificates;
using System.Text;
using System.Threading.Tasks;
using MimeKit;
using skyscraper5.Teletext.Wss;

namespace skyscraper8.Ietf.FLUTE
{
	public class FluteListener
	{
		public FluteListener(IPAddress destinationAddress, ushort destinationPort, ulong destinationTsi, ulong destinationToi)
		{
			DestinationAddress = destinationAddress;
			DestinationPort = destinationPort;
			DestinationTsi = destinationTsi;
			DestinationToi = destinationToi;
		}

		public IPAddress DestinationAddress { get; }
		public ushort DestinationPort { get; }
		public ulong DestinationTsi { get; }
		public ulong DestinationToi { get; }

		public DateTime LastTouched { get; private set; }

		public override string ToString()
		{
			if (_disabled)
			{
				return "<disabled>";
			}
			if (FileAssociation != null)
			{
				return String.Format("Filename={4}", DestinationAddress, DestinationPort, DestinationTsi, DestinationToi, FileAssociation.ContentLocation);
			}
			return String.Format("???");
		}

		internal void PushPacket(LctFrame lctFrame)
		{
			LastTouched = DateTime.Now;

			if (_disabled)
				return;

			if (lctFrame.LctHeader.FecObjectTransmissionInformation == null)
			{
				if (lctFrame.LctHeader.NipActualCarrierInformation != null)
				{
					CarrierInformation = lctFrame.LctHeader.NipActualCarrierInformation;
					return;
				}
			}


			if (transferLength == 0)
			{
				if (lctFrame.LctHeader.FecObjectTransmissionInformation == null)
				{
					return;
				}
				transferLength = (long)lctFrame.LctHeader.FecObjectTransmissionInformation.TransferLength;
			}

			if (blocks == null)
				blocks = new Dictionary<Tuple<ushort, ushort>, FluteBlock>();

			if (lctFrame.FecHeader == null)
				return;

			ushort sbn = lctFrame.FecHeader.SourceBlockNumber;
			ushort esi = lctFrame.FecHeader.EncodingSymbolId;

			//FluteBlock? fluteBlock = blocks.Find(x =>
				//x.SourceBlockNumer == sbn &&
				//x.EncodingSymbolId == esi);

			/*FluteBlock fluteBlock = null
			foreach (FluteBlock candidateBlock in blocks)
			{
				if (candidateBlock.SourceBlockNumer == sbn)
				{
					if (candidateBlock.EncodingSymbolId == esi)
					{
						fluteBlock = candidateBlock;
						break;
					}
				}
			}
			if (fluteBlock == null)
			{
				fluteBlock = new FluteBlock(sbn, esi, lctFrame.Payload);
				blocks.Add(fluteBlock);
				_dataWritten += (uint)fluteBlock.Payload.Length;
			}
			else
			{
				blocksDeduplicated++;
			}*/

			Tuple<ushort, ushort> coordinate = new Tuple<ushort, ushort>(sbn, esi);
			if (blocks.ContainsKey(coordinate))
			{
				blocksDeduplicated++;
				return;
			}

			FluteBlock fluteBlock = new FluteBlock(sbn, esi, lctFrame.Payload);
			blocks.Add(coordinate, fluteBlock);
			_dataWritten += (uint)fluteBlock.Payload.Length;

		}

		internal virtual bool IsComplete()
		{
			if (_disabled)
				return false;

			if (CarrierInformation != null)
				return true;

			if (transferLength == 0)
				return false;

			
			return DataWritten >= transferLength;
		}

		private long _dataWritten;
		public long DataWritten
		{
			get
			{
				if (blocks == null)
					return 0;

				if (_disabled)
					return 0;

				return _dataWritten;
			}
		}
		
		public virtual Stream ToStream()
		{
			if (!IsComplete())
				throw new InvalidOperationException();

			Stream level1;
			if (blocks.Count == 1)
			{
				level1 = new MemoryStream(blocks.Values.First().Payload);
			}
			else
			{
				List<FluteBlock> blockPayloads = blocks.Values.ToList();
				blockPayloads.Sort(new FluteBlockComparer());
				level1 = new FluteListenerStream(blockPayloads);
			}
			if (TrimmedLength.HasValue)
			{
				level1.SetLength(TrimmedLength.Value);
			}

			if (FileAssociation != null)
			{
				switch(FileAssociation.ContentEncoding)
				{
					case null:
					case "null":
					case "ll":
					case "nunull":
					case "nulull":
					case "nl":
						break;
					case "gzip":
						GZipStream level2 = new GZipStream(level1, CompressionMode.Decompress, false);
						return level2;
					default:
						throw new NotImplementedException(FileAssociation.ContentEncoding);
				}
			}
			return level1;
		}

		internal void WriteToFile(string filename)
		{
			FileInfo fi = new FileInfo(filename);
			if (fi.Exists)
				fi.Delete();
			FileStream fileStream = fi.OpenWrite();
			Stream sourceStream = ToStream();
			sourceStream.CopyTo(fileStream);
			sourceStream.Close();
			fileStream.Flush();
			fileStream.Close();
			fileStream.Dispose();
		}

		private long transferLength;
		private Dictionary<Tuple<ushort,ushort>,FluteBlock> blocks;
		private uint blocksDeduplicated;

		public class FluteBlock
		{
			public FluteBlock(ushort sourceBlockNumer, ushort encodingSymbolId, byte[] payload)
			{
				SourceBlockNumer = sourceBlockNumer;
				EncodingSymbolId = encodingSymbolId;
				Payload = payload;
			}

			public ushort SourceBlockNumer;
			public ushort EncodingSymbolId;
			public byte[] Payload;

			public override string ToString()
			{
				return String.Format("SBN={0}, ESI={1}", SourceBlockNumer, EncodingSymbolId);
			}
		}

		private FileType _fileAssocitation;
		public FileType FileAssociation
		{
			get
			{
				return _fileAssocitation;
			}
			set
			{
				_fileAssocitation = value;
				if (transferLength == 0)
				{
					if (_fileAssocitation.TransferLengthSpecified)
					{
						transferLength = (long)_fileAssocitation.TransferLength;
					}
					else if (_fileAssocitation.ContentLengthSpecified)
					{
						transferLength = (long)_fileAssocitation.ContentLength;
					}
				}
			}
		}

		public NipActualCarrierInformation CarrierInformation { get; private set; }

		private bool _disabled;
		public bool Disabled
		{
			get
			{
				return _disabled;
			}
			set
			{
				if (value)
				{
					transferLength = 0;
					if (blocks != null)
					{
						blocks.Clear();
						blocks = null;
					}
				}
				_disabled = value;
			}
		}

		public double DownloadProgress
		{
			get
			{
				double w = (double)DataWritten;
				if (w == 0)
					return 0;

				double g = (double)transferLength;
				double result = (w * 100.0) / g;
				result = Math.Round(result, 1);
				return result;
			}
		}

		public double LastReportedProgress { get; internal set; }
		public long? TrimmedLength { get; set; }
	}
}
