using skyscraper5.Mpeg2;
using skyscraper5.Mpeg2.Descriptors;
using skyscraper5.Skyscraper;
using skyscraper5.Skyscraper.IO;
using skyscraper5.src.InteractionChannel.Model.Descriptors;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net.NetworkInformation;
using System.Text;
using System.Threading.Tasks;
using skyscraper8.GSE.GSE;
using skyscraper8.InteractionChannel.Model2.Descriptors;
using Ionic.Crc;
using Ionic.Zlib;

namespace skyscraper5.src.InteractionChannel.Model
{
	public class Tim : Validatable
	{
		public Tim(bool broadcast, byte status, TsDescriptor[] descs)
		{
			this.Broadcast = broadcast;
			this.Status = status;
			this.Descriptors = descs;
		}

		public Tim(MemoryStream ms, bool broadcast)
		{
			Broadcast = broadcast;
			Status = ms.ReadUInt8();

			byte descriptorLoopCount = ms.ReadUInt8(); descriptorLoopCount++;
			int minimumRequiredLength = descriptorLoopCount * 2;
			if (ms.GetAvailableBytes() < minimumRequiredLength)
			{
				Valid = false;
				return;
			}
			Descriptors = new TsDescriptor[descriptorLoopCount];
			for (int i = 0; i < descriptorLoopCount; i++)
			{
				//we don't have a way to read single descriptors so far
				byte descriptorTag = ms.ReadUInt8();
				byte descriptorLength = ms.ReadUInt8();
				if (ms.GetAvailableBytes() < descriptorLength)
				{
					Valid = false;
					return;
				}
				byte[] descriptorBuffer = ms.ReadBytes(descriptorLength);
				switch(descriptorTag)
				{
					case 0x4a:
						//Linkage_descriptor 
						//must appear in RMT, not TIM
						Valid = false;
						return;
					case 0xa0:
						Descriptors[i] = new _0xa0_NetworkLayerInfoDescriptor(descriptorBuffer);
						break;
					case 0xa1:
						Descriptors[i] = new _0xa1_CorrectionMessageDescriptor(descriptorBuffer);
						break;
					case 0xa2:
						Descriptors[i] = new _0xa2_LoginInitializeDescriptor(descriptorBuffer);
						if (!Descriptors[i].Valid)
						{
							Valid = false;
							return;
						}
						break;
					case 0xa3:
						Descriptors[i] = new _0xa3_AcqAssignDescriptor(descriptorBuffer);
						if (!Descriptors[i].Valid)
						{
							Valid = false;
							return;
						}
						break;
					case 0xa4:
						Descriptors[i] = new _0xa4_SyncAssignDescriptor(descriptorBuffer);
						if (!Descriptors[i].Valid)
						{
							Valid = false;
							return;
						}
						break;
					case 0xa5:
						Descriptors[i] = new _0xa5_EncryptedLoginIdDescriptor(descriptorBuffer);
						if (!Descriptors[i].Valid)
						{
							Valid = false;
							return;
						}
						break;
					case 0xa7:
						//RCS_content_descriptor
						//can't appear in a TIM.
						Valid = false;
						return;
					case 0xa8:
						Descriptors[i] = new _0xa8_SatelliteForwardLinkDescriptor(descriptorBuffer);
						if (!Descriptors[i].Valid)
						{
							Valid = false;
							return;
						}
						break;
					case 0xa9:
						Descriptors[i] = new _0xa9_SatelliteReturnLinkDescriptor(descriptorBuffer);
						if (!Descriptors[i].Valid)
						{
							Valid = false;
							return;
						}
						break;
					case 0xaa:
						Descriptors[i] = new _0xaa_TableUpdateDescriptor(descriptorBuffer);
						if (!Descriptors[i].Valid)
						{
							Valid = false;
							return;
						}
						break;
					case 0xab:
						Descriptors[i] = new _0xab_ContentionControlDescriptor(descriptorBuffer);
						break;
					case 0xac:
						Descriptors[i] = new _0xac_CorrectionControlDescriptor(descriptorBuffer);
						break;
					case 0xad:
						Descriptors[i] = new _0xad_ForwardInteractionPathDescriptor(descriptorBuffer);
						if (!Descriptors[i].Valid)
						{
							Valid = false;
							return;
						}
						break;
					case 0xae:
						Descriptors[i] = new _0xae_ReturnInteractionPathDescriptor(descriptorBuffer);
						if (!Descriptors[i].Valid)
						{
							Valid = false;
							return;
						}
						break;
					case 0xb1:
						Descriptors[i] = new _0xb1_CorrectionMessageExtensionDescriptor(descriptorBuffer);
						if (!Descriptors[i].Valid)
						{
							Valid = false;
							return;
						}
						break;
					case 0xb5:
						Descriptors[i] = new _0xb5_ImplementationTypeDescriptor(descriptorBuffer);
						if (!Descriptors[i].Valid)
						{
							Valid = false;
							return;
						}
						break;
					case 0xaf:
						Descriptors[i] = new _0xaf_ConnectionControlDescriptor(descriptorBuffer);
						break;
					case 0xb2:
						Descriptors[i] = new _0xb2_ReturnTransmissionModesDescriptor(descriptorBuffer);
						if (!Descriptors[i].Valid)
						{
							Valid = false;
							return;
						}
						break;
					case 0xb3:
						Descriptors[i] = new _0xb3_MeshLogonInitializeDescriptor(descriptorBuffer);
						if (!Descriptors[i].Valid)
						{
							Valid = false;
							return;
						}
						break;
					case 0xb6:
						Descriptors[i] = new _0xb6_LlFecIdentifierDescriptor(descriptorBuffer);
						if (!Descriptors[i].Valid)
						{
							Valid = false;
							return;
						}
						break;
					case 0xb7:
						Descriptors[i] = new _0xb7_FramePayloadFormatDescriptor(descriptorBuffer);
						if (!Descriptors[i].Valid)
						{
							Valid = false;
							return;
						}
						break;
					case 0xb9:
						Descriptors[i] = new _0xb9_LogonResponseDescriptor(descriptorBuffer);
						if (!Descriptors[i].Valid)
						{
							Valid = false;
							return;
						}
						break;
					case 0xbb:
						Descriptors[i] = new _0xbb_LowerLayerServiceDescriptor(descriptorBuffer);
						if (!Descriptors[i].Valid)
						{
							Valid = false;
							return;
						}
						break;
					case 0xc4:
						Descriptors[i] = new _0xc4_HigherLayersInitializeDescriptor(descriptorBuffer);
						if (!Descriptors[i].Valid)
						{
							Valid = false;
							return;
						}
						break;
					default:
						if (descriptorTag >= 0x00 && descriptorTag <= 0x49)
						{
							//Reserved
							continue;
						}
						if (descriptorTag >= 0x4b && descriptorTag <= 0x9f)
						{
							//Reserved
							continue;
						}
						if (descriptorTag >= 0xcc && descriptorTag <= 0xdf)
						{
							//Reserved
							continue;
						}
						if (descriptorTag >= 0xe0 && descriptorTag <= 0xfe)
						{
							Descriptors[i] = new UserDefinedDescriptor(descriptorTag, descriptorBuffer);
							break;
						}
						if (descriptorTag == 0xff)
						{
							//Forbidden
							Valid = false;
							return;
						}
						throw new NotImplementedException(String.Format("TIM Parse 0x{0:X2}", descriptorTag));
				}
			}

			byte[] padBytes = ms.ReadBytes(ms.GetAvailableBytes() - 4);
			uint crc32 = ms.ReadUInt32BE();
			Valid = true;
		}

		public bool Broadcast { get; }
		public byte Status { get; }

		public TsDescriptor[] Descriptors { get; }

		public bool IDEncrypt => (Status & 0x80) != 0;
		public bool LogonFail => (!Broadcast & ((Status & 0x40) != 0));
		public bool LogonDenied => (!Broadcast & ((Status & 0x20) != 0));
		public bool Logoff => (!Broadcast & ((Status & 0x10) != 0));
		public bool TransmitDisable => (!Broadcast & ((Status & 0x08) != 0));
		public bool RainFadeRelease => (!Broadcast & ((Status & 0x04) != 0));
		public bool RainFadeDetect => (!Broadcast & ((Status & 0x02) != 0));
		public bool WakeUp => (!Broadcast & ((Status & 0x01) != 0));

		public bool CscLinkFailureRec => (Broadcast & ((Status & 0x10) != 0));
		public bool LinkFailureRecovery => (Broadcast & ((Status & 0x08) != 0));
		public bool ReturnLinkFailure => (Broadcast & ((Status & 0x04) != 0));
		public bool NccReceiveFailure => (Broadcast & ((Status & 0x02) != 0));
		public bool SchedulerFailure => (Broadcast & ((Status & 0x01) != 0));

		public override int GetHashCode()
		{
			int hash = 17;
			hash ^= HashCode.Combine(Broadcast, Status, Descriptors.Length);
			foreach (TsDescriptor tsDescriptor in Descriptors)
			{
				hash ^= tsDescriptor.GetDescriptorHashCode();
			}
			return hash;
		}

		public override bool Equals(object? obj)
		{
			if (obj == null)
				return false;
			Tim that = obj as Tim;
			if (that == null) 
				return false;

			if (this.Status != that.Status)
				return false;

			bool thisHasDescriptors = this.Descriptors != null;
			bool thatHasDescriptors = that.Descriptors != null;
			if (thisHasDescriptors != thatHasDescriptors)
				return false;

			if (this.Descriptors.Length != that.Descriptors.Length)
				return false;

			for (int i = 0; i < this.Descriptors.Length; i++)
			{
				TsDescriptor thisDescriptor = this.Descriptors[i];
				TsDescriptor thatDescriptor = that.Descriptors[i];

				byte thisDescriptorId = thisDescriptor.GetDescriptorId();
				byte thatDescriptorId = thatDescriptor.GetDescriptorId();

				if (thisDescriptorId != thatDescriptorId)
					return false;

				if (!thisDescriptor.DescriptorEquals(thatDescriptor))
				{
					return false;
				}
			}
			return true;
		}
	}
}
