using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.IO;
using skyscraper5.DsmCc.Descriptors;
using skyscraper5.Dvb;
using skyscraper5.Dvb.Descriptors;
using skyscraper5.Dvb.Descriptors.Extension;
using skyscraper5.Hdmv.Descriptors;
using skyscraper5.Mpeg2.Descriptors;
using skyscraper5.Mpeg2.ExtensionDescriptors;
using skyscraper5.Mpeg2.Psi.Model;
using skyscraper5.Skyscraper.IO;
using skyscraper5.src.Mpeg2.Descriptors;

namespace skyscraper5.Mpeg2.Psi
{
    class PmtParser : IPsiProcessor
    {
        public IPmtEventHandler EventHandler { get; }

        public PmtParser(IPmtEventHandler eventHandler)
        {
            EventHandler = eventHandler;
        }

        public void GatherPsi(PsiSection section, int sourcePid)
        {
            MemoryStream buffer = new MemoryStream(section.GetDataCopy());

            byte table_id = buffer.ReadUInt8();
            if (table_id != 0x02)
                return;

            ushort readUInt16Be = buffer.ReadUInt16BE();
            bool sectionSyntaxIndicator = (readUInt16Be & 0x8000) != 0;
            if (!sectionSyntaxIndicator)
            {
                //According to T-REC-H.222.0-201808-S!!PDF-E.pdf, page 71 - this is supposed to be set to false.
                return;
            }
            bool zero = (readUInt16Be & 0x4000) != 0;
            int reserved = (readUInt16Be & 0x3000) >> 12;
            int sectionLength = (readUInt16Be & 0x0fff);

            ushort programNumber = buffer.ReadUInt16BE();

            byte readUInt8 = buffer.ReadUInt8();
            int reserved2 = (readUInt8 & 0xc0) >> 6;
            int versionNumber = (readUInt8 & 0x3e);
            bool currentNextIndicator = (readUInt8 & 0x01) != 0;

            byte sectionNumber = buffer.ReadUInt8();

            byte lastSectionNumber = buffer.ReadUInt8();

            readUInt16Be = buffer.ReadUInt16BE();
            int reserved3 = (readUInt16Be & 0xe000) >> 13;
            int pcrPid = (readUInt16Be & 0x1fff);

            readUInt16Be = buffer.ReadUInt16BE();
            int reserved4 = (readUInt16Be & 0xf000) >> 12;
            int programInfoLength = (readUInt16Be & 0x0fff) & 0x3ff;

            ProgramMapping result = new ProgramMapping(programNumber, pcrPid);
            if (programInfoLength > 0)
            {
                byte[] descriptorBuffer = new byte[programInfoLength];
                int descriptorBufferFilling = buffer.Read(descriptorBuffer, 0, programInfoLength);
                if (descriptorBufferFilling != programInfoLength)
                    return;

                ParseOuterDescriptors(result, descriptorBuffer);
            }

            while ((buffer.Length - buffer.Position) > 4)
            {
                PmtStreamType streamType = (PmtStreamType)buffer.ReadUInt8();

                readUInt16Be = buffer.ReadUInt16BE();
                int reserved5 = (readUInt16Be & 0xe000) >> 13;
                int elementaryPid = (readUInt16Be & 0x1fff);

                readUInt16Be = buffer.ReadUInt16BE();
                int reserved6 = (readUInt16Be & 0xf000) >> 12;
                int esInfoLength = (readUInt16Be & 0xfff) & 0x3ff;
                if (esInfoLength > buffer.GetAvailableBytes())
                    return;
                
                byte[] descriptorBuffer2 = buffer.ReadBytes(esInfoLength);
                ProgramMappingStream streamChild = new ProgramMappingStream(streamType, elementaryPid);
                result.Streams.Add(streamChild);
                ParseInnerDescriptors(streamChild, descriptorBuffer2);
            }

            EventHandler?.PmtEvent(result, sourcePid);
        }

        private void ParseInnerDescriptors(ProgramMappingStream output, byte[] inputBuffer)
        {
            IEnumerable<TsDescriptor> descriptors = TsDescriptorUnpacker.GetInstance().UnpackDescriptors(inputBuffer, "PMT");
            foreach (TsDescriptor dvbDescriptor in descriptors)
            {
                output.NumDescriptors++;
                switch (dvbDescriptor.GetType().Name)
                {
                    case nameof(StreamIdentifierDescriptor):
                        output.ComponentTag = ((StreamIdentifierDescriptor)dvbDescriptor).ComponentTag;
                        break;
                    case nameof(Iso639LanguageDescriptor):
                        Iso639LanguageDescriptor iso639Language = (Iso639LanguageDescriptor)dvbDescriptor;
                        output.Iso639LanguageCode = iso639Language.Iso639LanguageCode;
                        output.AudioType = iso639Language.AudioType;
                        break;
                    case nameof(TeletextDescriptor):
                        TeletextDescriptor teletextDescriptor = (TeletextDescriptor)dvbDescriptor;
                        output.Teletexts = teletextDescriptor.Teletexts;
                        break;
                    case nameof(ApplicationSignallingDescriptor):
                        ApplicationSignallingDescriptor alApplicationSignallingDescriptor = (ApplicationSignallingDescriptor)dvbDescriptor;
                        if (output.Applications == null)
                            output.Applications = new List<ApplicationSignallingDescriptor.ApplicationSignal>();
                        output.Applications.AddRange(alApplicationSignallingDescriptor.Applications);
                        break;
                    case nameof(CarouselIdentifierDescriptor):
                        CarouselIdentifierDescriptor carouselIdentifierDescriptor = (CarouselIdentifierDescriptor)dvbDescriptor;
                        output.CarouselFormatId = carouselIdentifierDescriptor.FormatId;
                        output.CarouselId = carouselIdentifierDescriptor.CarouselId;
                        break;
                    case nameof(DataBroadcastIdDescriptor):
                        DataBroadcastIdDescriptor dataBroadcastIdDescriptor = (DataBroadcastIdDescriptor)dvbDescriptor;
                        output.DataBroadcastId = dataBroadcastIdDescriptor.DataBroadcastId;
                        output.DataBroadcastSelector = dataBroadcastIdDescriptor.DataBroadcastSelector;
                        output.IpMacNotificationInfo = dataBroadcastIdDescriptor.IpMacNotificationInfo;
                        break;
                    case nameof(SupplemenatryAudioDescriptor):
                        SupplemenatryAudioDescriptor supplemenatryAudioDescriptor = (SupplemenatryAudioDescriptor)dvbDescriptor;
                        output.SupplementaryIndependantStream = supplemenatryAudioDescriptor.MixType;
                        output.EditorialClassification = supplemenatryAudioDescriptor.EditorialClassification;
                        output.SupplementaryLanguageCode = supplemenatryAudioDescriptor.Iso639LanguageCode;
                        break;
                    case nameof(Ac3Descriptor):
                        Ac3Descriptor ac3Descriptor = (Ac3Descriptor)dvbDescriptor;
                        output.Asvc = ac3Descriptor.Asvc;
                        output.BSID = ac3Descriptor.BSID;
                        output.ComponentType = ac3Descriptor.ComponentType;
                        output.MainId = ac3Descriptor.MainId;
                        output.Ac3DescriptorPresent = true;
                        break;
                    case nameof(VideoStreamDescriptor):
                        VideoStreamDescriptor videoStreamDescriptor = (VideoStreamDescriptor)dvbDescriptor;
                        output.FrameRate = videoStreamDescriptor.FrameRate;
                        output.Mpeg1OnlyFlag = videoStreamDescriptor.Mpeg1OnlyFlag;
                        output.ChromaFormat = videoStreamDescriptor.ChromaFormat;
                        output.ConstrainedParameterFlag = videoStreamDescriptor.ConstrainedParameterFlag;
                        output.FrameRateExtensionFlag = videoStreamDescriptor.FrameRateExtensionFlag;
                        output.MultipleFramerateFlag = videoStreamDescriptor.MultipleFramerateFlag;
                        output.ProfileAndLevelIndication = videoStreamDescriptor.ProfileAndLevelIndication;
                        output.StillPictureFlag = videoStreamDescriptor.StillPictureFlag;
                        break;
                    case nameof(DataStreamAlignmentDescriptor):
                        output.AlignmentType = ((DataStreamAlignmentDescriptor)dvbDescriptor).AlignmentType;
                        break;
                    case nameof(MaximumBitrateDescriptor):
                        MaximumBitrateDescriptor maximumBitrateDescriptor = ((MaximumBitrateDescriptor)dvbDescriptor);
                        output.MaximumBitrate = maximumBitrateDescriptor.MaximumBitrate;
                        break;
                    case nameof(AudioStreamDescriptor):
                        AudioStreamDescriptor audioStreamDescriptor = ((AudioStreamDescriptor)dvbDescriptor);
                        output.AudioStreamId = audioStreamDescriptor.ID;
                        output.FreeFormat = audioStreamDescriptor.FreeFormat;
                        output.Layer = audioStreamDescriptor.Layer;
                        output.VariableRateAudio = audioStreamDescriptor.VariableRateAudio;
                        break;
                    case nameof(AvcVideoDescriptor):
                        AvcVideoDescriptor avcVideoDescriptor = (AvcVideoDescriptor)dvbDescriptor;
                        output.Avc24HourPictureFlag = avcVideoDescriptor.Avc24HourPictureFlag;
                        output.AvcCompatibleFlags = avcVideoDescriptor.AvcCompatibleFlags;
                        output.AvcStillPresent = avcVideoDescriptor.AvcStillPresent;
                        output.ConstraintSet0Flag = avcVideoDescriptor.ConstraintSet0Flag;
                        output.ConstraintSet1Flag = avcVideoDescriptor.ConstraintSet1Flag;
                        output.ConstraintSet2Flag = avcVideoDescriptor.ConstraintSet2Flag;
                        output.ConstraintSet3Flag = avcVideoDescriptor.ConstraintSet3Flag;
                        output.ConstraintSet4Flag = avcVideoDescriptor.ConstraintSet4Flag;
                        output.ConstraintSet5Flag = avcVideoDescriptor.ConstraintSet5Flag;
                        output.FramePackingSeiNotPresent = avcVideoDescriptor.FramePackingSeiNotPresent;
                        output.LevelIdc = avcVideoDescriptor.LevelIdc;
                        output.ProfileRdc = avcVideoDescriptor.ProfileRdc;
                        break;
                    case nameof(CaDescriptor):
                        CaDescriptor caDescriptor = (CaDescriptor)dvbDescriptor;
                        output.CaPid = caDescriptor.CaPid;
                        output.CaSystemId = caDescriptor.CaSystemId;
                        output.CaPrivateData = caDescriptor.PrivateData;
                        break;
                    case nameof(UserDefinedDescriptor):
                        if (!output.PrivateDataSpecifier.HasValue)
                        {
                            if (output.UnknownUserDefines == null)
                            {
                                output.UnknownUserDefines = new List<UserDefinedDescriptor>();
                            }
                            output.UnknownUserDefines.Add((UserDefinedDescriptor)dvbDescriptor);
                            continue;
                        }
                        TsDescriptor userDefinedDescriptor = UserDefinedDescriptorUnpacker.GetInstance().UnpackUserDefinedDescriptor((UserDefinedDescriptor)dvbDescriptor, output.PrivateDataSpecifier.Value, "PMT");
                        if (userDefinedDescriptor == null)
                        {
							if (output.UnknownUserDefines == null)
							{
								output.UnknownUserDefines = new List<UserDefinedDescriptor>();
							}
							output.UnknownUserDefines.Add((UserDefinedDescriptor)dvbDescriptor);
							continue;
						}
                        throw new NotImplementedException(nameof(UserDefinedDescriptor));
                    case nameof(PrivateDataSpecifierDescriptor):
                        output.PrivateDataSpecifier = ((PrivateDataSpecifierDescriptor)dvbDescriptor).PrivateDataSpecifier;
                        break;
                    case nameof(SubtitlingDescriptor):
                        output.Subtitlings = ((SubtitlingDescriptor)dvbDescriptor).Subtitlings;
                        break;
                    case nameof(RegistrationDescriptor):
                        RegistrationDescriptor rd = ((RegistrationDescriptor)dvbDescriptor);
                        output.FormatIdentifier = rd.FormatIdentifier;
                        output.AdditionalIdentificationInfo = rd.AdditionalIdentificationInfo;
                        break;
                    case nameof(AssociationTagDescriptor):
                        AssociationTagDescriptor atd = (AssociationTagDescriptor)dvbDescriptor;
                        output.AssociationTagSelector = atd.Selector;
                        output.AssociationTagTransactionId = atd.TransactionId;
                        output.AssociationTagUse = atd.Use;
                        output.AssociationTagPrivateData = atd.PrivateData;
                        output.AssociationTagTimeOut = atd.TimeOut;
                        break;
                    case nameof(VbiDataDescriptor):
                        output.VbiData = ((VbiDataDescriptor)dvbDescriptor).VbiData;
                        break;
                    case nameof(AacDescriptor):
                        AacDescriptor aacDescriptor = ((AacDescriptor)dvbDescriptor);
                        output.AacType = aacDescriptor.AacType;
                        output.AacAdditionalInfo =  aacDescriptor.AdditionalInfo;
                        output.AacProfileAndLevel = aacDescriptor.ProfileAndLevel;
                        output.SaocDeFlag = aacDescriptor.SaocDeFlag;
                        break;
                    case nameof(AncillaryDataDescriptor):
                        AncillaryDataDescriptor acd = (AncillaryDataDescriptor)dvbDescriptor;
                        output.AncillaryDataDescriptor = ((AncillaryDataDescriptor)dvbDescriptor);
                        break;
                    case nameof(EnhancedAc3Descriptor):
                        EnhancedAc3Descriptor eac3d = ((EnhancedAc3Descriptor)dvbDescriptor);
                        output.MainId = eac3d.MainId;
                        output.BSID = eac3d.BSID;
                        if (eac3d.ComponentType != null)
                            output.ComponentType = (Ac3Descriptor.ComponentTypeEnum)eac3d.ComponentType;
                        output.Asvc = eac3d.ASVC;
                        output.MixInfoExists = eac3d.MixInfoExists;
                        output.Substream1 = eac3d.Substream1;
                        output.Substream2 = eac3d.Substream2;
                        output.Substream3 = eac3d.Substream3;
                        output.Ac3PlusDescriptorPresent = true;
                        break;
                    case nameof(HevcVideoDescriptor):
                        HevcVideoDescriptor hvd = (HevcVideoDescriptor)dvbDescriptor;
                        output.Copied44bits = hvd.Copied44bits;
                        output.LevelIdc = hvd.LevelIdc;
                        output.FrameOnlyConstraintFlag = hvd.FrameOnlyConstraintFlag;
                        output.HdrWcgIdc = hvd.HdrWcgIdc;
                        output.Hevc24hrPicturePresentFlag = hvd.Hevc24hrPicturePresentFlag;
                        output.HevcStillPresentFlag = hvd.HevcStillPresentFlag;
                        output.InterlacedSourceFlag = hvd.InterlacedSourceFlag;
                        output.NonPackedConstraintFlag = hvd.NonPackedConstraintFlag;
                        output.ProfileCompatibilityIndication = hvd.ProfileCompatibilityIndication;
                        output.ProfileIdc = hvd.ProfileIdc;
                        output.ProfileSpace = hvd.ProfileSpace;
                        output.ProgressiveSourceFlag = hvd.ProgressiveSourceFlag;
                        output.SubPicHrdParamsNotPresentFlag = hvd.SubPicHrdParamsNotPresentFlag;
                        output.TemporalIdMax = hvd.TemporalIdMax;
                        output.TierFlag = hvd.TierFlag;
                        output.TemporalIdMin = hvd.TemporalIdMin;
                        break;
                    case nameof(Ac4Descriptor):
                        Ac4Descriptor ac4d = (Ac4Descriptor)dvbDescriptor;
                        output.Ac4ChannelMode = ac4d.Ac4ChannelMode;
                        output.Ac4DialogEnhancement = ac4d.Ac4DialogEnhancementEnabled;
                        output.Ac4Dsi = ac4d.Ac4Dsi;
                        break;
                    case nameof(AudioPreselectionDescriptor):
                        output.AudioPreselection = ((AudioPreselectionDescriptor)dvbDescriptor).Preselections;
                        break;
                    case nameof(StdDescriptor):
                        output.LeakValidFlag = ((StdDescriptor)dvbDescriptor).LeakValidFlag;
                        break;
                    case nameof(VbiTeletextDescriptor):
                        VbiTeletextDescriptor vbiTeletext = ((VbiTeletextDescriptor)dvbDescriptor);
                        if (vbiTeletext.Teletexts == null)
                            break;
                        output.VbiTeletexts = vbiTeletext.Teletexts;
                        break;
                    case nameof(FmcDescriptor):
                        FmcDescriptor fd = ((FmcDescriptor)dvbDescriptor);
                        output.FlexMuxChannels = fd.FlexMuxChannels;
                        break;
                    case nameof(MvcExtensionDescriptor):
                        MvcExtensionDescriptor med = (MvcExtensionDescriptor)dvbDescriptor;
                        throw new NotImplementedException(nameof(MvcExtensionDescriptor));
                    case nameof(J2KVideoDescriptor):
                        J2KVideoDescriptor j2kvd = (J2KVideoDescriptor)dvbDescriptor;
                        throw new NotImplementedException(nameof(J2KVideoDescriptor));
                    case nameof(Mpeg4TextDescriptor):
                        Mpeg4TextDescriptor mtd = (Mpeg4TextDescriptor)dvbDescriptor;
                        if (mtd.FormatSpecificTextConfig == null)
                            break;
                        throw new NotImplementedException(nameof(Mpeg4TextDescriptor));
                    case nameof(SmoothingBufferDescriptor):
                        SmoothingBufferDescriptor sbd = (SmoothingBufferDescriptor)dvbDescriptor;
                        output.SbLeakRate = sbd.SbLeakRate;
                        output.SbSize = sbd.SbSize;
                        break;
                    case nameof(PrivateDataIndicatorDescriptor):
                        PrivateDataIndicatorDescriptor pdid = (PrivateDataIndicatorDescriptor)dvbDescriptor;
                        output.PrivateDataIndicator = pdid.PrivateDataIndicator;
                        break;
                    case nameof(HierarchyDescriptor):
                        HierarchyDescriptor hd = (HierarchyDescriptor)dvbDescriptor;
                        if (!hd.Valid)
                            break;
                        throw new NotImplementedException(nameof(HierarchyDescriptor));
                    case nameof(HevcTimingAndHrdDescriptor):
                        HevcTimingAndHrdDescriptor hahd = (HevcTimingAndHrdDescriptor)dvbDescriptor;
                        output._90khzFlag =  hahd._90khzFlag;
                        output.PictureAndTimingInfoPresentFlag = hahd.PictureAndTimingInfoPresentFlag;
                        output.HdrManagementValidFlag = hahd.HrdManagementValidFlag;
                        output.K = hahd.K;
                        output.N = hahd.N;
                        output.NumUnitsInTick = hahd.NumUnitsInTick;
                        output.TargetScheduleIdx = hahd.TargetScheduleIdx;
                        output.TargetScheduleIdxNotPresentFlag = hahd.TargetScheduleIdxNotPresentFlag;
                        break;
                    case nameof(AdaptionFieldDataDescriptor):
                        output.AdaptionFieldDataIdentifier = ((AdaptionFieldDataDescriptor)dvbDescriptor).AdaptionFieldDataIdentifier;
                        break;
                    case nameof(AvcTimingAndHrdDescriptor):
                        AvcTimingAndHrdDescriptor atahd = (AvcTimingAndHrdDescriptor)dvbDescriptor;
                        output._90khzFlag = atahd._90khzFlag;
                        output.PictureAndTimingInfoPresentFlag = atahd.PictureAndTimingInfoPresentFlag;
                        output.NumUnitsInTick = atahd.NumUnitsInTick;
                        output.K = atahd.K;
                        output.N = atahd.N;
                        output.HdrManagementValidFlag = atahd.HrdManagementValidFlag;
                        output.FixedFrameRateFlag = atahd.FixedFrameRateFlag;
                        output.PictureToDisplayConversionFlag = atahd.PictureToDisplayConversionFlag;
                        output.TemporalPocFlag = atahd.TemporalPocFlag;
                        break;
                    case nameof(Mpeg4AudioDescriptor):
                        output.Mpeg4AudioProfileAndLevel = ((Mpeg4AudioDescriptor)dvbDescriptor).Mpeg4AudioProfileAndLevel;
                        break;
                    case nameof(Mpeg2AacAudioDescriptor):
                        Mpeg2AacAudioDescriptor mpeg2aacad = ((Mpeg2AacAudioDescriptor)dvbDescriptor);
                        output.AacChannelConfiguration = mpeg2aacad.AacChannelConfiguration;
                        output.AacAdditionalInformation = mpeg2aacad.AacAdditionalInformation;
                        output.AacProfile = mpeg2aacad.AacProfile;
                        break;
                    case nameof(ScramblingDescriptor):
                        ScramblingDescriptor sd = ((ScramblingDescriptor)dvbDescriptor);
                        output.ScramblingMode = sd.ScramblingMode;
                        break;
                    case nameof(RelatedContentDescriptor):
                        output.RelatedContentDescriptorPresent = true;
                        break;
                    case nameof(_0x11_T2MIDescriptor):
                        _0x11_T2MIDescriptor t2mid = (_0x11_T2MIDescriptor)dvbDescriptor;
                        output.NumT2MiStreams = t2mid.NumT2MiStreams;
                        output.T2MiStreamId = t2mid.T2MiStreamId;
                        output.PcrIscrCommonClockFlag = t2mid.PcrIscrCommonClockFlag;
                        break;
                    case nameof(ExternalEsIdDescriptor):
                        if (!dvbDescriptor.Valid)
                            break;
                        throw new NotImplementedException(nameof(ExternalEsIdDescriptor));
                    case nameof(_0x2E_Mpeg4AudioExtensionDescriptor):
                        _0x2E_Mpeg4AudioExtensionDescriptor mp4audioExtension = (_0x2E_Mpeg4AudioExtensionDescriptor)dvbDescriptor;
                        output.AudioSpecifConfig = mp4audioExtension.AudioSpecificConfig;
                        output.AudioProfileLevelIndication = mp4audioExtension.AudioProfileLevelIndication;
                        break;
                    case nameof(MetadataDescriptor):
                        MetadataDescriptor metadataDescriptor = (MetadataDescriptor)dvbDescriptor;
                        output.MetadataApplicationFormat = metadataDescriptor.MetadataApplicationFormat;
                        output.MetadataApplicationFormatIdentifier = metadataDescriptor.MetadataApplicationFormatIdentifier;
                        output.MetadataFormat = metadataDescriptor.MetadataFormat;
                        output.MetadataFormatIdentifier = metadataDescriptor.MetadataFormatIdentifier;
                        output.MetadataServiceId = metadataDescriptor.MetadataServiceId;
                        output.DecoderConfigFlag = metadataDescriptor.DecoderConfigFlag;
                        output.ServiceIdentificationRecord = metadataDescriptor.ServiceIdentificationRecord;
                        output.DecoderConfig = metadataDescriptor.DecoderConfig;
                        output.DecoderConfigIdentificationRecord = metadataDescriptor.DecoderConfigIdentificationRecord;
                        output.DecoderConfigMetadataServiceId = metadataDescriptor.DecoderConfigMetadataServiceId;
                        output.Reserved = metadataDescriptor.Reserved;
                        output.PrivateData = metadataDescriptor.PrivateData;
                        break;
					default:
                        throw new NotImplementedException(dvbDescriptor.GetType().Name);
                }
            }
        }

        private void ParseOuterDescriptors(ProgramMapping output, byte[] inputBuffer)
        {
            IEnumerable<TsDescriptor> descriptors = TsDescriptorUnpacker.GetInstance().UnpackDescriptors(inputBuffer, "PMT");
            foreach (TsDescriptor tsDescriptor in descriptors)
            {
                string name = tsDescriptor.GetType().Name;
                switch (name)
                {
                    case nameof(MaximumBitrateDescriptor):
                        output.MaximumBitrate = ((MaximumBitrateDescriptor)tsDescriptor).MaximumBitrate;
                        break;
                    case nameof(MultiplexBufferUtilizationDescriptor):
                        MultiplexBufferUtilizationDescriptor mbud = (MultiplexBufferUtilizationDescriptor)tsDescriptor;
                        output.MultiplexBufferUtilizationBoundValidFlag = mbud.BoundValidFlag;
                        output.MultiplexLtwOffsetLowerBound = mbud.LtwOffsetLowerBound;
                        output.MultiplexLtwOffsetUpperBound = mbud.LtwOffsetUpperBound;
                        break;
                    case nameof(SystemClockDescriptor):
                        SystemClockDescriptor scd = (SystemClockDescriptor)tsDescriptor;
                        output.ClockAccuracyExponent = scd.ClockAccuracyExponent;
                        output.ClockAccuracyInteger = scd.ClockAccuracyInteger;
                        output.ExternalClockReferenceIndicator = scd.ExternalClockReferenceIndicator;
                        break;
                    case nameof(SmoothingBufferDescriptor):
                        SmoothingBufferDescriptor sbd = (SmoothingBufferDescriptor)tsDescriptor;
                        output.SbLeakRate = sbd.SbLeakRate;
                        output.SbSize = sbd.SbSize;
                        break;
                    case nameof(CaDescriptor):
                        CaDescriptor caDescriptor = (CaDescriptor)tsDescriptor;
                        output.CaPid = caDescriptor.CaPid;
                        output.CaSystemId = caDescriptor.CaSystemId;
                        output.CaPrivateData = caDescriptor.PrivateData;
                        break;
                    case nameof(PrivateDataSpecifierDescriptor):
                        output.PrivateDataSpecifier = ((PrivateDataSpecifierDescriptor)tsDescriptor).PrivateDataSpecifier;
                        break;
                    case nameof(UserDefinedDescriptor):
                        uint registry;
                        if (output.PrivateDataSpecifier.HasValue)
                            registry = output.PrivateDataSpecifier.Value;
                        else if (output.RegistrationFormatIdentifier.HasValue)
                            registry = output.RegistrationFormatIdentifier.Value;
                        else
                            break;

                         TsDescriptor userDefinedDescriptor = UserDefinedDescriptorUnpacker.GetInstance().UnpackUserDefinedDescriptor((UserDefinedDescriptor)tsDescriptor, registry, "PMT");
                         if (userDefinedDescriptor == null)
                             continue;
                        name = userDefinedDescriptor.GetType().Name;
                        switch(name)
                        {
                            case nameof(CopyControlDescriptor):
                                CopyControlDescriptor ccd = (CopyControlDescriptor)userDefinedDescriptor;
                                output.CaSystemId = ccd.CaSystemId;
                                if (ccd.CaSystemId == 0x0fff)
                                    output.CopyControlPrivateData = ((CopyControlDescriptor.AacsPrivateData)ccd.PrivateData).OriginalData;
                                else
                                    output.CopyControlPrivateData = (byte[])ccd.PrivateData;
                                break;
                            default:
                                throw new NotImplementedException(name);
                        }
                        break;
                    case nameof(RegistrationDescriptor):
                        RegistrationDescriptor registrationDescriptor = ((RegistrationDescriptor)tsDescriptor);
                        output.RegistrationFormatIdentifier = registrationDescriptor.FormatIdentifier;
                        output.RegistrationAdditionalIdentificationInfo = registrationDescriptor.AdditionalIdentificationInfo;
                        break;
                    case nameof(ServiceMoveDescriptor):
                        ServiceMoveDescriptor smd = (ServiceMoveDescriptor)tsDescriptor;
                        output.NewOriginalNetworkId = smd.NewOriginalNetworkId;
                        output.NewServiceId = smd.NewServiceId;
                        output.NewTransportStreamId = smd.NewTransportStreamId;
                        break;
                    case nameof(StreamIdentifierDescriptor):
                        StreamIdentifierDescriptor sid = (StreamIdentifierDescriptor)tsDescriptor;
                        output.ComponentTag = sid.ComponentTag;
                        break;
                    case nameof(MetadataStdDescriptor):
                        MetadataStdDescriptor mstdd = (MetadataStdDescriptor)tsDescriptor;
                        output.MetadataInputLeakRate = mstdd.MetadataInputLeakRate;
                        output.MetadataBufferSize = mstdd.MetadataBufferSize;
                        output.MetadataOutputLeakRate = mstdd.MetadataOutputLeakRate;
                        break;
                    case nameof(ScramblingDescriptor):
                        output.ScramblingMode = ((ScramblingDescriptor)tsDescriptor).ScramblingMode;
                        break;
                    case nameof(DeferredAssociationTagsDescriptor):
                        DeferredAssociationTagsDescriptor datd = (DeferredAssociationTagsDescriptor)tsDescriptor;
                        if (datd.AssociationTag == null)
                            break;
                        throw new NotImplementedException(nameof(DeferredAssociationTagsDescriptor));
                    case nameof(SLDescriptor):
                        output.EsId = ((SLDescriptor)tsDescriptor).EsId;
                        break;
                    case nameof(TeletextDescriptor):
                        //This should be in the inner loops.
                        break;
                    case nameof(Mpeg4TextDescriptor):
                        Mpeg4TextDescriptor mp4TextDescriptor = (Mpeg4TextDescriptor)tsDescriptor;
                        if (mp4TextDescriptor.FormatSpecificTextConfig == null)
                            break;
                        throw new NotImplementedException(nameof(Mpeg4TextDescriptor));
                    case nameof(PrivateDataIndicatorDescriptor):
                        PrivateDataIndicatorDescriptor pdid = (PrivateDataIndicatorDescriptor)tsDescriptor;
                        output.PrivateDataIndicator = pdid.PrivateDataIndicator;
                        break;
                    case nameof(VideoStreamDescriptor):
                        VideoStreamDescriptor vsd = (VideoStreamDescriptor)tsDescriptor;
                        output.FrameRate = vsd.FrameRate;
                        output.ChromaFormat = vsd.ChromaFormat;
                        output.ConstrainedParameterFlag = vsd.ConstrainedParameterFlag;
                        output.FrameRateExtensionFlag = vsd.FrameRateExtensionFlag;
                        output.Mpeg1OnlyFlag = vsd.Mpeg1OnlyFlag;
                        output.MultipleFramerateFlag = vsd.MultipleFramerateFlag;
                        output.ProfileAndLevelIndication = vsd.ProfileAndLevelIndication;
                        output.StillPictureFlag = vsd.StillPictureFlag;
                        break;
                    case nameof(TargetBackgroundGridDescriptor):
                        TargetBackgroundGridDescriptor tbgd = (TargetBackgroundGridDescriptor)tsDescriptor;
                        throw new NotImplementedException(nameof(TargetBackgroundGridDescriptor));
                    case nameof(MosaicDescriptor):
                        MosaicDescriptor md = (MosaicDescriptor)tsDescriptor;
                        throw new NotImplementedException(nameof(MosaicDescriptor));
                    case nameof(Iso639LanguageDescriptor):
                        Iso639LanguageDescriptor i639ld = (Iso639LanguageDescriptor)tsDescriptor;
                        output.AudioType = i639ld.AudioType;
                        output.Iso639LanguageCode = i639ld.Iso639LanguageCode;
                        break;
                    case nameof(IodDescriptor):
                        IodDescriptor iodD = (IodDescriptor)tsDescriptor;
                        output.InitialObjectDescriptor = iodD.InitialObjectDescriptor;
                        output.ScopeOfIodLabel = iodD.ScopeOfIodLabel;
                        output.IodLabel = iodD.IodLabel;
                        break;
                    case nameof(MetadataPointerDescriptor):
                        MetadataPointerDescriptor mpd = (MetadataPointerDescriptor)tsDescriptor;
                        output.MetadataProgramNumber = mpd.ProgramNumber;
                        output.MetadataApplicationFormat = mpd.MetadataApplicationFormat;
                        output.MetadataApplicationFormatIdentifier = mpd.MetadataApplicationFormatIdentifier;
                        output.MetadataFormat = mpd.MetadataFormat;
                        output.MetadataFormatIdentifier = mpd.MetadataFormatIdentifier;
                        output.MetadataLocatorRecord = mpd.MetadataLocatorRecord;
                        output.MetadataLocatorRecordFlag = mpd.MetadataLocatorRecordFlag;
                        output.MetadataServiceId = mpd.MetadataServiceId;
                        output.MpegCarriageFlag = mpd.MpegCarriageFlag;
                        output.MetadataPrivateData = mpd.PrivateData;
                        output.TransportStreamId = mpd.TransportStreamId;
                        output.TransportStreamLocation = mpd.TransportStreamLocation;
                        break;
                    case nameof(AudioStreamDescriptor):
                        AudioStreamDescriptor asd = (AudioStreamDescriptor)tsDescriptor;
                        output.FreeFormat = asd.FreeFormat;
                        output.AudioStreamId = asd.ID;
                        output.Layer = asd.Layer;
                        output.VariableRateAudio = asd.VariableRateAudio;
                        break;
                    case nameof(DataStreamAlignmentDescriptor):
                        DataStreamAlignmentDescriptor dsad = (DataStreamAlignmentDescriptor)tsDescriptor;
                        output.AlignmentType = dsad.AlignmentType;
                        break;
                    case nameof(HierarchyDescriptor):
                        HierarchyDescriptor hd = (HierarchyDescriptor)tsDescriptor;
                        output.HierarchyType = hd.HierarchyType;
                        output.HierarchyLayerType = hd.HierarchyLayerType;
                        output.TrefPresentFlag = hd.TrefPresentFlag;
                        output.HierarchyChannel = hd.HierarchyChannel;
                        output.NoViewScalabilityFlag = hd.NoViewScalabilityFlag;
                        output.HierarchyEmbeddedLayerIndex = hd.HierarchyEmbeddedLayerIndex;
                        output.NoQualityScalabilityFlag = hd.NoQualityScalabilityFlag;
                        output.NoSpatialScalabilityFlag = hd.NoSpatialScalabilityFlag;
                        output.NoTemporalScalabilityFlag = hd.NoTemporalScalabilityFlag;
                        break;
                    case nameof(ApplicationSignallingDescriptor):
                        ApplicationSignallingDescriptor asd2 = (ApplicationSignallingDescriptor)tsDescriptor;
                        if (!asd2.Valid)
                            break;
                        throw new NotImplementedException(nameof(ApplicationSignallingDescriptor));
                    default:
                        throw new NotImplementedException(name);
                }
                
            }

        }
    }
}
