using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.IO;
using System.Reflection;
using skyscraper5.Dvb.Descriptors;
using skyscraper5.Dvb.Descriptors.Extension;
using skyscraper5.Dvb.Psi.Model;
using skyscraper5.Mpeg2;
using skyscraper5.Mpeg2.Descriptors;
using skyscraper5.Skyscraper.IO;
using skyscraper5.Skyscraper.Plugins;

namespace skyscraper5.Dvb.Psi
{
    public class NitParser : IPsiProcessor
    {
        public INitEventHandler EventHandler { get; }

        public NitParser(INitEventHandler eventHandler)
        {
            EventHandler = eventHandler;
        }

        public void GatherPsi(PsiSection section, int sourcePid)
        {
            MemoryStream ms = new MemoryStream(section.GetDataCopy());
            byte tableId = ms.ReadUInt8();
            bool actualNetwork;
            switch (tableId)
            {
                case 0x40:
                    actualNetwork = true;
                    break;
                case 0x41:
                    actualNetwork = false;
                    break;
                case 0x72:
                    //stuffing area
                    return;
                default:
                    return;
            }

            ushort readUInt16Be = ms.ReadUInt16BE();
            bool sectionSyntaxSelector = (readUInt16Be & 0x8000) != 0;
            if (!sectionSyntaxSelector)
                return;
            int sectionLength = readUInt16Be & 0x0fff;

            ushort networkId = ms.ReadUInt16BE();
            NitNetwork nitNetwork = new NitNetwork(networkId);
            if (actualNetwork)
            {
                //I'd trust the NIT more than the EIT.
                EventHandler.SetNetworkId(networkId,true);
            }

            byte readUInt8 = ms.ReadUInt8();
            int versionNumber = (readUInt8 & 0x3e) >> 1;
            bool currentNextIndicator = (readUInt8 & 0x01) != 0;

            byte sectionNumber = ms.ReadUInt8();

            byte lastSectionNumber = ms.ReadUInt8();

            readUInt16Be = ms.ReadUInt16BE();
            int networkDescriptorsLength = readUInt16Be & 0x0fff;
            byte[] networkDescriptorsBuffer = ms.ReadBytes(networkDescriptorsLength);
            IEnumerable<TsDescriptor> networkDescriptors = TsDescriptorUnpacker.GetInstance().UnpackDescriptors(networkDescriptorsBuffer, "NIT");
            foreach (TsDescriptor dvbDescriptor in networkDescriptors)
            {
                switch (dvbDescriptor.GetType().Name)
                {
                    case nameof(NetworkNameDescriptor):
                        NetworkNameDescriptor networkNameDescriptor = (NetworkNameDescriptor)dvbDescriptor;
                        nitNetwork.Name = networkNameDescriptor.NetworkName;
                        break;
                    case nameof(LinkageDescriptor):
                        LinkageDescriptor linkageDescriptor = (LinkageDescriptor)dvbDescriptor;
                        nitNetwork.Linkages.Add(linkageDescriptor);
                        break;
                    case nameof(PrivateDataSpecifierDescriptor):
                        nitNetwork.PrivateDataSpecifierId = ((PrivateDataSpecifierDescriptor)dvbDescriptor).PrivateDataSpecifier;
                        break;
                    case nameof(UserDefinedDescriptor):
                        if (!nitNetwork.PrivateDataSpecifierId.HasValue)
                            break;
                        TsDescriptor descriptor = UserDefinedDescriptorUnpacker.GetInstance().UnpackUserDefinedDescriptor((UserDefinedDescriptor)dvbDescriptor, nitNetwork.PrivateDataSpecifierId.Value, "NIT");
                        if (descriptor == null)
                            break;
                        switch (descriptor)
                        {
                            default:
                                throw new NotImplementedException(descriptor.GetType().Name);
                        }
                        break;
                    case nameof(CellListDescriptor):
                        nitNetwork.Cells = ((CellListDescriptor)dvbDescriptor).Cells;
                        break;
                    case nameof(XaitPidDescriptor):
                        XaitPidDescriptor xpd = (XaitPidDescriptor)dvbDescriptor;
                        nitNetwork.XaitPid = xpd.XaitPid;
                        break;
                    case nameof(MultilingualNetworkNameDescriptor):
                        MultilingualNetworkNameDescriptor mnnd = (MultilingualNetworkNameDescriptor)dvbDescriptor;
                        nitNetwork.MultilingualNetworkName = mnnd.MultilingualNetworkName;
                        break;
                    case nameof(TargetRegionNameDescriptor):
                        TargetRegionNameDescriptor trnd = (TargetRegionNameDescriptor)dvbDescriptor;
                        nitNetwork.RegionNames = trnd.Regions;
                        nitNetwork.RegionNameCountryCode = trnd.CountryCode;
                        nitNetwork.RegionNameLanguageCode = trnd.Iso639LanguageCode;
                        break;
                    case nameof(TargetRegionDescriptor):
                        TargetRegionDescriptor trd = (TargetRegionDescriptor)dvbDescriptor;
                        nitNetwork.Regions = trd.TargetRegions;
                        nitNetwork.RegionCountryCode = trd.CountryCode;
                        break;
                    case nameof(MessageDescriptor):
                        MessageDescriptor message = (MessageDescriptor)dvbDescriptor;
                        if (nitNetwork.Messages == null)
                            nitNetwork.Messages = new HashSet<MessageDescriptor>();
                        nitNetwork.Messages.Add(message);
                        break;
                    case nameof(UriLinkageDescriptor):
                        UriLinkageDescriptor uriLinkage = (UriLinkageDescriptor)dvbDescriptor;
                        nitNetwork.MinPollingInterval = uriLinkage.MinPollingInterval;
                        nitNetwork.Uri = uriLinkage.Uri;
                        nitNetwork.UriLinkageType = uriLinkage.UriLinkageType;
                        break;
                    case nameof(ServiceListDescriptor):
                        ServiceListDescriptor serviceList = (ServiceListDescriptor)dvbDescriptor;
                        nitNetwork.ServiceList = serviceList.Services;
                        break;
                    case nameof(ExtensionUserDefinedDescriptor):
                        ExtensionUserDefinedDescriptor eudd = (ExtensionUserDefinedDescriptor)dvbDescriptor;
                        //might be everything... idk...
                        break;
                    default:
                        throw new NotImplementedException(dvbDescriptor.GetType().Name);
                }
            }
            
            readUInt16Be = ms.ReadUInt16BE();
            int transportStreamLoopLength = readUInt16Be & 0x0fff;
            byte[] transportStreamsBuffer = ms.ReadBytes(transportStreamLoopLength);
			


            uint crc32 = ms.ReadUInt32BE();

			ReadOnlyCollection<NitTransportStream> nitTransportStreams = UnpackTransportStreams(networkId, transportStreamsBuffer).ToList().AsReadOnly();
            nitNetwork.Streams = nitTransportStreams;
			EventHandler.OnNitNetwork(nitNetwork);
            foreach (NitTransportStream stream in nitTransportStreams)
            {
                EventHandler.OnNitTransportStream(networkId, stream);
            }
		}

        private IEnumerable<NitTransportStream> UnpackTransportStreams(ushort networkId, byte[] buffer)
        {
            MemoryStream ms = new MemoryStream(buffer, false);
            while ((ms.Length - ms.Position) > 6)
            {
                ushort transportStreamId = ms.ReadUInt16BE();
                ushort originalNetworkId = ms.ReadUInt16BE();
                NitTransportStream child = new NitTransportStream(originalNetworkId, transportStreamId);

                ushort readUInt16Be = ms.ReadUInt16BE();
                int transportDescriptorsLength = readUInt16Be & 0x0fff;
                if (transportDescriptorsLength > ms.GetAvailableBytes())
                    yield break;
                byte[] transportDescriptorBuffer = ms.ReadBytes(transportDescriptorsLength);
                IEnumerable<TsDescriptor> descriptors = TsDescriptorUnpacker.GetInstance().UnpackDescriptors(transportDescriptorBuffer, "NIT");
                foreach (TsDescriptor dvbDescriptor in descriptors)
                {
                    switch (dvbDescriptor.GetType().Name)
                    {
                        case nameof(SatelliteDeliverySystemDescriptor):
                            SatelliteDeliverySystemDescriptor satelliteDeliverySystemDescriptor = (SatelliteDeliverySystemDescriptor)dvbDescriptor;
                            child.East = satelliteDeliverySystemDescriptor.East;
                            child.FecInner = satelliteDeliverySystemDescriptor.FecInner;
                            child.Frequency = satelliteDeliverySystemDescriptor.Frequency;
                            child.ModulationType = satelliteDeliverySystemDescriptor.ModulationType;
                            child.OrbitalPosition = satelliteDeliverySystemDescriptor.OrbitalPosition;
                            child.Polarization = satelliteDeliverySystemDescriptor.Polarization;
                            child.RollOff = satelliteDeliverySystemDescriptor.RollOff;
                            child.S2 = satelliteDeliverySystemDescriptor.S2;
                            child.SymbolRate = satelliteDeliverySystemDescriptor.SymbolRate;
                            child.DeliveryMethod = NitTransportStream.TransportMedium.DVB_S;
                            break;
                        case nameof(ServiceListDescriptor):
                            ServiceListDescriptor sld = (ServiceListDescriptor)dvbDescriptor;
                            child.Services = sld;
                            break;
                        case nameof(S2SatelliteDeliverySystemDescriptor):
                            S2SatelliteDeliverySystemDescriptor s2sdsd = (S2SatelliteDeliverySystemDescriptor)dvbDescriptor;
                            child.ScramblingSequenceIndex = s2sdsd.ScramblingSequenceIndex;
                            child.InputStreamIdentifier = s2sdsd.InputStreamIdentifier;
                            child.TimesliceNumber = s2sdsd.TimesliceNumber;
                            child.TsGsMode = s2sdsd.TsGsMode;
                            if (child.East.HasValue)
                            {
                                child.S2 = true;
                                child.DeliveryMethod = NitTransportStream.TransportMedium.DVB_S2;
                            }

                            break;
                        case nameof(PrivateDataSpecifierDescriptor):
                            child.PrivateDataSpecifierId = ((PrivateDataSpecifierDescriptor)dvbDescriptor).PrivateDataSpecifier;
                            break;
                        case nameof(UserDefinedDescriptor):
                            if (!child.PrivateDataSpecifierId.HasValue)
                                break;
                            TsDescriptor unpacked = UserDefinedDescriptorUnpacker.GetInstance().UnpackUserDefinedDescriptor((UserDefinedDescriptor)dvbDescriptor, child.PrivateDataSpecifierId.Value, "NIT");
                            if (unpacked == null)
                                break;
                            HandleUserDefinedDescriptor(unpacked, child);
                            break;
                        case nameof(LinkageDescriptor):
                            child.Linkages.Add((LinkageDescriptor)dvbDescriptor);
                            break;
                        case nameof(T2DeliverySystemDescriptor):
                            child.DeliveryMethod = NitTransportStream.TransportMedium.DVB_T2;
                            T2DeliverySystemDescriptor t2dsd = (T2DeliverySystemDescriptor)dvbDescriptor;
                            child.TfsFlag = t2dsd.TfsFlag;
                            child.Bandwidth = t2dsd.Bandwidth;
                            child.CellInfos = t2dsd.CellInfos;
                            child.GuardInterval = t2dsd.GuardInterval;
                            child.OtherFrequencyFlag = t2dsd.OtherFrequencyFlag;
                            child.PlpId = t2dsd.PlpId;
                            child.SisoMiso = t2dsd.SisoMiso;
                            child.T2SystemId = t2dsd.T2SystemId;
                            child.TransmissionMode = t2dsd.TransmissionMode;
                            break;
                        case nameof(FrequencyListDescriptor):
                            FrequencyListDescriptor fld = (FrequencyListDescriptor)dvbDescriptor;
                            child.CodingType = fld.CodingType;
                            child.CentreFrequencies = fld.CentreFrequencies;
                            break;
                        case nameof(CellListDescriptor):
                            CellListDescriptor cld = (CellListDescriptor)dvbDescriptor;
                            child.Cells = cld.Cells;
                            break;
                        case nameof(CableDeliverySystemDescriptor):
                            CableDeliverySystemDescriptor cdsd = (CableDeliverySystemDescriptor)dvbDescriptor;
                            child.Frequency = cdsd.Frequency;
                            child.SymbolRate = cdsd.SymbolRate;
                            child.FecInner = cdsd.FecInner;
                            child.FecOuter = cdsd.FecOuter;
                            child.ModulationType = cdsd.Modulation;
                            child.DeliveryMethod = NitTransportStream.TransportMedium.DVB_C;
                            break;
                        case nameof(TerristialDeliverySystemDescriptor):
                            TerristialDeliverySystemDescriptor tdsd = (TerristialDeliverySystemDescriptor)dvbDescriptor;
                            child.Bandwidth = tdsd.Bandwidth;
                            child.Frequency = tdsd.CentreFrequency;
                            child.CodeRateHpStream = tdsd.CodeRateHpStream;
                            child.CodeRateLpStream = tdsd.CodeRateLpStream;
                            child.ModulationType = tdsd.Constellation;
                            child.GuardInterval = tdsd.GuardInterval;
                            child.HierarchyInformation = tdsd.HierarchyInformation;
                            child.MpeFecIndicator = tdsd.MpeFecIndicator;
                            child.OtherFrequencyFlag = tdsd.OtherFrequencyFlag;
                            child.Priority = tdsd.Priority;
                            child.TimeSlicingIndicator = tdsd.TimeSlicingIndicator ? 1 : 0;
                            child.TransmissionMode = tdsd.TransmissionMode;
                            break;
                        case nameof(NetworkNameDescriptor):
                            NetworkNameDescriptor nnd = (NetworkNameDescriptor)dvbDescriptor;
                            child.NetworkName = nnd.NetworkName;
                            break;
                        case nameof(TargetRegionDescriptor):
                            TargetRegionDescriptor trd = (TargetRegionDescriptor)dvbDescriptor;
                            child.TargetRegions = trd.TargetRegions;
                            child.TargetRegionCountryCode = trd.CountryCode;
                            break;
                        case nameof(CellFrequencyLinkDescriptor):
                            CellFrequencyLinkDescriptor cfld = (CellFrequencyLinkDescriptor)dvbDescriptor;
                            child.CellFrequencies = cfld.Cells;
                            break;
                        default:
                            throw new NotImplementedException(dvbDescriptor.GetType().Name);
                    }
                }

                yield return child;
                //EventHandler.OnNitTransportStream(networkId, child);
            }
        }

        private void HandleUserDefinedDescriptor(TsDescriptor unpacked, NitTransportStream outputTs)
        {
			Attribute customAttribute = unpacked.GetType().GetCustomAttribute(typeof(DescriptorPluginNitHandlerAttribute));
			if (customAttribute == null)
			{
				throw new NotImplementedException(String.Format("{0} has no {1} defined.", unpacked.GetType().Name, nameof(DescriptorPluginNitHandlerAttribute)));
			}

			DescriptorPluginNitHandlerAttribute handlerAttribute = (DescriptorPluginNitHandlerAttribute)customAttribute;
			handlerAttribute.Handler.HandleNit(outputTs, unpacked);
		}
    }
}
