using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
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;

namespace skyscraper5.Dvb.Psi
{
    class SdtParser : IPsiProcessor
    {
        public ISdtEventHandler EventHandler { get; }

        public SdtParser(ISdtEventHandler eventHandler)
        {
            EventHandler = eventHandler;
        }

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

            byte tableId = ms.ReadUInt8();
            if (tableId != 0x42 && tableId != 0x46)
                return;

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

            ushort transportStreamId = ms.ReadUInt16BE();

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

            byte sectionNumber = ms.ReadUInt8();

            byte lastSectionNumber = ms.ReadUInt8();

            ushort originalNetworkId = ms.ReadUInt16BE();
            ms.ReadUInt8(); //reserved future use

            if (tableId == 0x42)
            {
                //0x42 meaning current mux
                EventHandler.SetNetworkId(originalNetworkId);
                EventHandler.SetTransportStreamId(transportStreamId);
            }

            while (ms.GetAvailableBytes() > 4)
            {
                ReadService(ms, transportStreamId, originalNetworkId);
            }

            uint crc32 = ms.ReadUInt32BE();
        }

        private void ReadService(MemoryStream ms, ushort transportStreamId, ushort originalNetworkId)
        {
            ushort serviceId = ms.ReadUInt16BE();

            byte readUInt8 = ms.ReadUInt8();
            bool eitScheduleFlag = (readUInt8 & 0x02) != 0;
            bool eitPresentFollowingFlag = (readUInt8 & 0x01) != 0;

            ushort readUInt16Be = ms.ReadUInt16BE();
            int rs = (readUInt16Be & 0xe000) >> 13;
            RunningStatus runningStatus = (RunningStatus)rs;
            bool freeCaMode = (readUInt16Be & 0x1000) != 0;
            int descriptorsLoopLength = readUInt16Be & 0xfff;

            SdtService child = new SdtService(serviceId, eitScheduleFlag, eitPresentFollowingFlag, runningStatus, freeCaMode);
            
            byte[] descriptorsData = ms.ReadBytes(descriptorsLoopLength);
            IEnumerable<TsDescriptor> unpackDescriptors = TsDescriptorUnpacker.GetInstance().UnpackDescriptors(descriptorsData, "SDT");
            foreach (TsDescriptor dvbDescriptor in unpackDescriptors)
            {
                string name = dvbDescriptor.GetType().Name;
                switch (name)
                {
                    case nameof(ServiceDescriptor):
                        ServiceDescriptor serviceDescriptor = (ServiceDescriptor)dvbDescriptor;
                        child.ServiceName = serviceDescriptor.ServiceName;
                        child.ServiceProviderName = serviceDescriptor.ServiceProviderName;
                        child.ServiceType = serviceDescriptor.ServiceType;
                        break;
                    case nameof(UserDefinedDescriptor):
                        if (!child.PrivateDataSpecifier.HasValue)
                            break;
                        TsDescriptor tsDescriptor = UserDefinedDescriptorUnpacker.GetInstance().UnpackUserDefinedDescriptor((UserDefinedDescriptor)dvbDescriptor, child.PrivateDataSpecifier.Value, "SDT");
                        if (tsDescriptor == null)
                            break;
                        ParseUserDefinedDescriptor(tsDescriptor, child);
                        break;
                    case nameof(PrivateDataSpecifierDescriptor):
                        child.PrivateDataSpecifier = ((PrivateDataSpecifierDescriptor)dvbDescriptor).PrivateDataSpecifier;
                        break;
                    case nameof(CaIdentifierDescriptor):
                        child.CaIdentifiers = ((CaIdentifierDescriptor)dvbDescriptor).CaSystemIds;
                        if (child.CaIdentifiers.Length > 1)
                            Array.Sort(child.CaIdentifiers);
                        break;
                    case nameof(ComponentDescriptor):
                        ComponentDescriptor cd = (ComponentDescriptor)dvbDescriptor;
                        if (child.Components == null)
                            child.Components = new List<ComponentDescriptor>();
                        child.Components.Add(cd);
                        break;
                    case nameof(DataBroadcastDescriptor):
                        DataBroadcastDescriptor dbd = (DataBroadcastDescriptor)dvbDescriptor;
                        child.ComponentTag = dbd.ComponentTag;
                        child.DataBroadcastId = dbd.DataBroadcastId;
                        child.Selector = dbd.Selector;
                        child.Iso639LanguageCode = dbd.Iso639LanguageCode;
                        child.Text = dbd.Text;
                        break;
                    case nameof(CountryAvailabilityDescriptor):
                        CountryAvailabilityDescriptor cad = (CountryAvailabilityDescriptor)dvbDescriptor;
                        foreach (string cadCountryCode in cad.CountryCodes)
                        {
                            if (child.CountryAvailability == null)
                                child.CountryAvailability = new Dictionary<string, bool>();
                            child.CountryAvailability.Add(cadCountryCode, cad.CountryAvailabilityFlag);
                        }
                        break;
                    case nameof(LinkageDescriptor):
                        LinkageDescriptor ld = (LinkageDescriptor)dvbDescriptor;
                        if (child.Linkages == null)
                            child.Linkages = new HashSet<LinkageDescriptor>();
                        child.Linkages.Add(ld);
                        break;
                    case nameof(TimeShiftedServiceDescriptor):
                        child.ReferenceServiceId = ((TimeShiftedServiceDescriptor)dvbDescriptor).ReferenceServiceId;
                        break;
                    case nameof(NvodReferenceDescriptor):
                        child.NvodReferences = ((NvodReferenceDescriptor)dvbDescriptor).NvodReferences;
                        break;
                    case nameof(ExtensionUserDefinedDescriptor):
                        if (!child.PrivateDataSpecifier.HasValue)
                            break;
                        TsDescriptor extTsDescriptor = UserDefinedDescriptorUnpacker.GetInstance().UnpackUserDefinedDescriptor((UserDefinedDescriptor)dvbDescriptor, child.PrivateDataSpecifier.Value, "SDT");
                        if (extTsDescriptor == null)
                            break;
                        ParseUserDefinedDescriptor(extTsDescriptor, child);
                        break;
                    case nameof(DefaultAuthorityDescriptor):
                        DefaultAuthorityDescriptor defaultAuthorityDescriptor = (DefaultAuthorityDescriptor)dvbDescriptor;
                        child.DefaultAuthority = defaultAuthorityDescriptor.DefaultAuthority;
                        break;
                    case nameof(MultilingualServiceNameDescriptor):
                        MultilingualServiceNameDescriptor msnd = (MultilingualServiceNameDescriptor)dvbDescriptor;
                        child.MultilingualServiceName = msnd.ServiceNames;
                        break;
                    case nameof(FtaContentManagementDescriptor):
                        FtaContentManagementDescriptor fsnd = (FtaContentManagementDescriptor)dvbDescriptor;
                        child.ControlRemoteAccessOverInternet = fsnd.ControlRemoteAccessOverInternet;
                        child.DoNotApplyRevocation = fsnd.DoNotApplyRevocation;
                        child.DoNotScramble = fsnd.DoNotScramble;
                        break;
                    case nameof(ServiceRelocatedDescriptor):
                        ServiceRelocatedDescriptor serviceRelocatedDescriptor = (ServiceRelocatedDescriptor)dvbDescriptor;
                        child.OldOriginalNetworkId = serviceRelocatedDescriptor.OldOriginalNetworkId;
                        child.OldServiceId = serviceRelocatedDescriptor.OldServiceId;
                        child.OldTransportStreamId = serviceRelocatedDescriptor.OldTransportStreamId;
                        break;
                    case nameof(MessageDescriptor):
                        if (child.Messages == null)
                            child.Messages = new List<MessageDescriptor>();

                        MessageDescriptor message = (MessageDescriptor)dvbDescriptor;
                        child.Messages.Add(message);
                        break;
                    default:
                        throw new NotImplementedException(name);
                }
            }

            EventHandler.OnSdtService(transportStreamId, originalNetworkId, child);
        }

        private void ParseUserDefinedDescriptor(TsDescriptor input, SdtService output)
        {
            if (input == null)
                return;

            string name = input.ToString();
            switch (name)
            {
                default:
                    throw new NotImplementedException(name);
            }
        }

        private void ParseUserDefinedExtensionDescriptor(TsDescriptor input, SdtService output)
        {
            if (input == null)
                return;

            string name = input.ToString();
            switch (name)
            {
                default:
                    throw new NotImplementedException(name);
            }
        }
    }
}
