using skyscraper5.Mpeg2;
using skyscraper5.Skyscraper;
using skyscraper5.Skyscraper.IO;
using skyscraper5.src.InteractionChannel.Model.Descriptors;
using skyscraper5.src.Skyscraper.Scraper.Storage.Utilities;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace skyscraper5.src.InteractionChannel.Model
{
	public class Rmt : Validatable
	{
		private static TsDescriptorUnpacker tsDescriptorUnpacker;

		public Rmt(MemoryStream ms, bool gseMode = false, ushort gseNetworkId = 0x0000)
		{
			if (tsDescriptorUnpacker == null)
				tsDescriptorUnpacker = TsDescriptorUnpacker.GetInstance();

			if (!gseMode)
			{
				byte tableId = ms.ReadUInt8();
				if (tableId != 0x41)
				{
					Valid = false;
					return;
				}

				byte byteA = ms.ReadUInt8();
				bool sectionSyntaxIndicator = (byteA & 0x80) != 1;
				if (!sectionSyntaxIndicator)
				{
					Valid = false;
					return;
				}

				bool reservedFutureUse = (byteA & 0x40) != 1;
				int reserved = (byteA & 0x30) >> 4;

				int sectionLength = (byteA & 0x0f);
				sectionLength <<= 8;
				sectionLength += ms.ReadUInt8();
				if (sectionLength != ms.GetAvailableBytes())
				{
					Valid = false;
					return;
				}

				NetworkId = ms.ReadUInt16BE();

				byte byteB = ms.ReadUInt8();
				reserved = (byteB & 0xc0) >> 6;
				int versionNumber = (byteB & 0x3e) >> 1;
				bool currentNextIndicator = (byteB & 0x01) != 0;

				byte sectionNumber = ms.ReadUInt8();
				byte lastSectionNumber = ms.ReadUInt8();
			}
			else
			{
				NetworkId = gseNetworkId;
			}

			byte byteC = ms.ReadUInt8();
			int reservedFutureUseB = (byteC & 0xf0) >> 4;
			int networkDescriptorsLength = (byteC & 0x0f);
			networkDescriptorsLength += ms.ReadUInt8();

			if (ms.GetAvailableBytes() < networkDescriptorsLength)
			{
				Valid = false;
				return;
			}

			byte[] networkDescriptors = ms.ReadBytes(networkDescriptorsLength);
			IEnumerable<TsDescriptor> enumerable = tsDescriptorUnpacker.UnpackDescriptors(networkDescriptors, "RMT");
			foreach(TsDescriptor descriptor in enumerable)
			{
				if (!descriptor.Valid)
				{
					Valid = false;
					return;
				}
				if (descriptor.GetDescriptorId() == 0x4a)
				{
					if (Linkages == null)
						Linkages = new List<_0x4a_LinkageDescriptor> ();
					Linkages.Add((_0x4a_LinkageDescriptor)descriptor);
					continue;
				}

				throw new NotImplementedException(String.Format("Unknown descriptor 0x{0:X2} in RCS Map Table. If this is reproducible, consider submitting a sample of this stream."));
			}

			byte byteD = ms.ReadUInt8();
			int reservedFutureUseC = (byteD & 0xf0) >> 4;
			int transportStreamLoopLength = (byteD & 0x0f);
			transportStreamLoopLength <<= 8;
			transportStreamLoopLength += ms.ReadUInt8();
			byte[] transportStreamLoop = ms.ReadBytes(transportStreamLoopLength);
			TransportStreams = ReadTransportStreamLoop(transportStreamLoop);

			Valid = true;
		}

		private IReadOnlyList<TransportStream> ReadTransportStreamLoop(byte[] transportStreamLoop)
		{
			List<TransportStream> result = new List<TransportStream>();

			MemoryStream ms = new MemoryStream(transportStreamLoop);
			while (ms.GetAvailableBytes() >= 6)
			{
				TransportStream child = new TransportStream();
				result.Add(child);
				child.TransportStreamId = ms.ReadUInt16BE();
				child.OriginalNetworkId = ms.ReadUInt16BE();

				byte byteD = ms.ReadUInt8();
				int reservedFutureUseC = (byteD & 0xf0) >> 4;
				int transportDescriptorsLength = (byteD & 0x0f);
				transportDescriptorsLength <<= 8;
				transportDescriptorsLength += ms.ReadUInt8();
				if (transportDescriptorsLength > 0)
				{
					byte[] networkDescriptors = ms.ReadBytes(transportDescriptorsLength);
					IEnumerable<TsDescriptor> enumerable = tsDescriptorUnpacker.UnpackDescriptors(networkDescriptors, "RMT");
					foreach (TsDescriptor descriptor in enumerable)
					{
						if (!descriptor.Valid)
						{
							child.Valid = false;
							return result.AsReadOnly();
						}
						byte id = descriptor.GetDescriptorId();
						switch (id)
						{
							case 0xa8:
								if (child.SatelliteForwardLink != null)
									throw new NotImplementedException("multiple forward links?");
								child.SatelliteForwardLink = (_0xa8_SatelliteForwardLinkDescriptor)descriptor;
								break;
							case 0xa9:
								if (child.SatelliteReturnLink != null)
									throw new NotImplementedException("multiple return links?");
								child.SatelliteReturnLink = (_0xa9_SatelliteReturnLinkDescriptor)descriptor;
								break;
							default:
								throw new NotImplementedException(String.Format("Unknown descriptor 0x{0:X2} in RCS Map Table. If this is reproducible, consider submitting a sample of this stream."));
						}
					}
				}
				child.Valid = true;
			}

			return result.AsReadOnly();
		}

		public ushort NetworkId { get; }
		public List<_0x4a_LinkageDescriptor> Linkages { get; }
		public IReadOnlyList<TransportStream> TransportStreams { get; }

		public class TransportStream : Validatable
		{
			public ushort TransportStreamId { get; internal set; }
			public ushort OriginalNetworkId { get; internal set; }
			public _0xa9_SatelliteReturnLinkDescriptor SatelliteReturnLink { get; internal set; }
			public _0xa8_SatelliteForwardLinkDescriptor SatelliteForwardLink { get; internal set; }

			public override string ToString()
			{
				return String.Format("ONID {0}, TSID {1}", OriginalNetworkId, TransportStreamId);
			}

			public DatabaseKeyRmtTransportStream ToKey(ushort networkId)
			{
				return new DatabaseKeyRmtTransportStream(networkId, TransportStreamId, OriginalNetworkId);
			}

			protected bool Equals(TransportStream other)
			{
				if (this.TransportStreamId != other.TransportStreamId)
					return false;
				if (this.OriginalNetworkId != other.OriginalNetworkId)
					return false;
				if (this.SatelliteForwardLink != null)
				{
					if (other.SatelliteForwardLink != null)
					{
						if (!this.SatelliteForwardLink.Equals(other.SatelliteForwardLink))
							return false;
					}
					else
					{
						return false;
					}
				}
				else
				{
					if (other.SatelliteForwardLink != null)
						return false;
				}
				if (this.SatelliteReturnLink != null)
				{
					if (other.SatelliteReturnLink != null)
					{
						if (!this.SatelliteReturnLink.Equals(other.SatelliteReturnLink))
							return false;
					}
					else
					{
						return false;
					}
				}
				else
				{
					if (other.SatelliteReturnLink != null)
						return false;
				}

				return true;
			}

			public override bool Equals(object? obj)
			{
				if (ReferenceEquals(null, obj)) 
					return false;
				if (ReferenceEquals(this, obj)) 
					return true;
				if (obj.GetType() != this.GetType()) 
					return false;
				return Equals((TransportStream)obj);
			}

			public override int GetHashCode()
			{
				return HashCode.Combine(TransportStreamId, OriginalNetworkId, SatelliteReturnLink, SatelliteForwardLink);
			}
		}
	}
}
